Merge "Incorporate ANC slice into the Volume Panel button" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 4c235e4..85323c3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -159,7 +159,6 @@
 aconfig_declarations {
     name: "com.android.window.flags.window-aconfig",
     package: "com.android.window.flags",
-    container: "system",
     srcs: ["core/java/android/window/flags/*.aconfig"],
 }
 
@@ -172,9 +171,7 @@
 // DeviceStateManager
 aconfig_declarations {
     name: "android.hardware.devicestate.feature.flags-aconfig",
-    exportable: true,
     package: "android.hardware.devicestate.feature.flags",
-    container: "system",
     srcs: ["core/java/android/hardware/devicestate/feature/*.aconfig"],
 }
 
@@ -187,9 +184,7 @@
 // Input
 aconfig_declarations {
     name: "com.android.hardware.input.input-aconfig",
-    exportable: true,
     package: "com.android.hardware.input",
-    container: "system",
     srcs: ["core/java/android/hardware/input/*.aconfig"],
 }
 
@@ -209,7 +204,6 @@
 aconfig_declarations {
     name: "com.android.text.flags-aconfig",
     package: "com.android.text.flags",
-    container: "system",
     srcs: ["core/java/android/text/flags/*.aconfig"],
 }
 
@@ -228,7 +222,6 @@
 aconfig_declarations {
     name: "android.location.flags-aconfig",
     package: "android.location.flags",
-    container: "system",
     srcs: [
         "location/java/android/location/flags/*.aconfig",
     ],
@@ -250,7 +243,6 @@
 aconfig_declarations {
     name: "android.nfc.flags-aconfig",
     package: "android.nfc",
-    container: "system",
     srcs: ["nfc/java/android/nfc/*.aconfig"],
 }
 
@@ -281,7 +273,6 @@
 aconfig_declarations {
     name: "android.security.flags-aconfig",
     package: "android.security",
-    container: "system",
     srcs: ["core/java/android/security/*.aconfig"],
 }
 
@@ -302,7 +293,6 @@
 aconfig_declarations {
     name: "android.app.usage.flags-aconfig",
     package: "android.app.usage",
-    container: "system",
     srcs: ["core/java/android/app/usage/*.aconfig"],
 }
 
@@ -386,7 +376,6 @@
 aconfig_declarations {
     name: "android.companion.virtualdevice.flags-aconfig",
     package: "android.companion.virtualdevice.flags",
-    container: "system",
     srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
 }
 
@@ -399,7 +388,6 @@
 aconfig_declarations {
     name: "android.companion.virtual.flags-aconfig",
     package: "android.companion.virtual.flags",
-    container: "system",
     srcs: ["core/java/android/companion/virtual/*.aconfig"],
 }
 
@@ -407,7 +395,6 @@
 aconfig_declarations {
     name: "android.view.inputmethod.flags-aconfig",
     package: "android.view.inputmethod",
-    container: "system",
     srcs: ["core/java/android/view/inputmethod/flags.aconfig"],
 }
 
@@ -421,7 +408,6 @@
 aconfig_declarations {
     name: "android.os.vibrator.flags-aconfig",
     package: "android.os.vibrator",
-    container: "system",
     srcs: ["core/java/android/os/vibrator/*.aconfig"],
 }
 
@@ -435,7 +421,6 @@
 aconfig_declarations {
     name: "android.view.flags-aconfig",
     package: "android.view.flags",
-    container: "system",
     srcs: ["core/java/android/view/flags/*.aconfig"],
 }
 
@@ -454,7 +439,6 @@
 aconfig_declarations {
     name: "android.view.accessibility.flags-aconfig",
     package: "android.view.accessibility",
-    container: "system",
     srcs: ["core/java/android/view/accessibility/flags/*.aconfig"],
 }
 
@@ -472,9 +456,7 @@
 // Hardware
 aconfig_declarations {
     name: "android.hardware.flags-aconfig",
-    exportable: true,
     package: "android.hardware.flags",
-    container: "system",
     srcs: ["core/java/android/hardware/flags/*.aconfig"],
 }
 
@@ -488,7 +470,6 @@
 aconfig_declarations {
     name: "android.widget.flags-aconfig",
     package: "android.widget.flags",
-    container: "system",
     srcs: ["core/java/android/widget/flags/*.aconfig"],
 }
 
@@ -508,7 +489,6 @@
 aconfig_declarations {
     name: "android.content.pm.flags-aconfig",
     package: "android.content.pm",
-    container: "system",
     srcs: ["core/java/android/content/pm/flags.aconfig"],
 }
 
@@ -529,7 +509,6 @@
 aconfig_declarations {
     name: "android.content.res.flags-aconfig",
     package: "android.content.res",
-    container: "system",
     srcs: ["core/java/android/content/res/*.aconfig"],
 }
 
@@ -550,7 +529,6 @@
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
     package: "com.android.media.flags",
-    container: "system",
     srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
 }
 
@@ -570,9 +548,7 @@
 // Media Editing
 aconfig_declarations {
     name: "com.android.media.flags.editing-aconfig",
-    exportable: true,
     package: "com.android.media.editing.flags",
-    container: "system",
     srcs: [
         "media/java/android/media/flags/editing.aconfig",
     ],
@@ -588,7 +564,6 @@
 aconfig_declarations {
     name: "com.android.media.flags.projection-aconfig",
     package: "com.android.media.projection.flags",
-    container: "system",
     srcs: [
         "media/java/android/media/flags/projection.aconfig",
     ],
@@ -618,9 +593,7 @@
 // Media TV
 aconfig_declarations {
     name: "android.media.tv.flags-aconfig",
-    exportable: true,
     package: "android.media.tv.flags",
-    container: "system",
     srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"],
 }
 
@@ -633,9 +606,7 @@
 // OnDeviceIntelligence
 aconfig_declarations {
     name: "android.app.ondeviceintelligence-aconfig",
-    exportable: true,
     package: "android.app.ondeviceintelligence.flags",
-    container: "system",
     srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"],
 }
 
@@ -649,7 +620,6 @@
 aconfig_declarations {
     name: "android.permission.flags-aconfig",
     package: "android.permission.flags",
-    container: "system",
     srcs: ["core/java/android/permission/flags.aconfig"],
 }
 
@@ -669,7 +639,6 @@
 aconfig_declarations {
     name: "android.database.sqlite-aconfig",
     package: "android.database.sqlite",
-    container: "system",
     srcs: ["core/java/android/database/sqlite/*.aconfig"],
 }
 
@@ -688,9 +657,7 @@
 // Biometrics
 aconfig_declarations {
     name: "android.hardware.biometrics.flags-aconfig",
-    exportable: true,
     package: "android.hardware.biometrics",
-    container: "system",
     srcs: ["core/java/android/hardware/biometrics/flags.aconfig"],
 }
 
@@ -742,7 +709,6 @@
 aconfig_declarations {
     name: "android.multiuser.flags-aconfig",
     package: "android.multiuser",
-    container: "system",
     srcs: ["core/java/android/content/pm/multiuser.aconfig"],
 }
 
@@ -756,7 +722,6 @@
 aconfig_declarations {
     name: "android.app.flags-aconfig",
     package: "android.app",
-    container: "system",
     srcs: ["core/java/android/app/*.aconfig"],
 }
 
@@ -769,9 +734,7 @@
 // Broadcast Radio
 aconfig_declarations {
     name: "android.hardware.radio.flags-aconfig",
-    exportable: true,
     package: "android.hardware.radio",
-    container: "system",
     srcs: ["core/java/android/hardware/radio/*.aconfig"],
 }
 
@@ -785,7 +748,6 @@
 aconfig_declarations {
     name: "android.credentials.flags-aconfig",
     package: "android.credentials.flags",
-    container: "system",
     srcs: ["core/java/android/credentials/flags.aconfig"],
     exportable: true,
 }
@@ -806,9 +768,7 @@
 // Content Protection
 aconfig_declarations {
     name: "android.view.contentprotection.flags-aconfig",
-    exportable: true,
     package: "android.view.contentprotection.flags",
-    container: "system",
     srcs: ["core/java/android/view/contentprotection/flags/*.aconfig"],
 }
 
@@ -822,7 +782,6 @@
 aconfig_declarations {
     name: "com.android.server.flags.services-aconfig",
     package: "com.android.server.flags",
-    container: "system",
     srcs: ["services/core/java/com/android/server/flags/*.aconfig"],
 }
 
@@ -835,9 +794,7 @@
 // App prediction
 aconfig_declarations {
     name: "android.service.appprediction.flags-aconfig",
-    exportable: true,
     package: "android.service.appprediction.flags",
-    container: "system",
     srcs: ["core/java/android/service/appprediction/flags/*.aconfig"],
 }
 
@@ -850,9 +807,7 @@
 // Controls
 aconfig_declarations {
     name: "android.service.controls.flags-aconfig",
-    exportable: true,
     package: "android.service.controls.flags",
-    container: "system",
     srcs: ["core/java/android/service/controls/flags/*.aconfig"],
 }
 
@@ -865,9 +820,7 @@
 // Voice
 aconfig_declarations {
     name: "android.service.voice.flags-aconfig",
-    exportable: true,
     package: "android.service.voice.flags",
-    container: "system",
     srcs: ["core/java/android/service/voice/flags/*.aconfig"],
 }
 
@@ -881,7 +834,6 @@
 aconfig_declarations {
     name: "android.service.autofill.flags-aconfig",
     package: "android.service.autofill",
-    container: "system",
     srcs: [
         "services/autofill/bugfixes.aconfig",
         "services/autofill/features.aconfig",
@@ -897,9 +849,7 @@
 // Companion
 aconfig_declarations {
     name: "android.companion.flags-aconfig",
-    exportable: true,
     package: "android.companion",
-    container: "system",
     srcs: ["core/java/android/companion/*.aconfig"],
 }
 
@@ -912,9 +862,7 @@
 // Networking
 aconfig_declarations {
     name: "android.net.platform.flags-aconfig",
-    exportable: true,
     package: "android.net.platform.flags",
-    container: "system",
     srcs: ["core/java/android/net/flags.aconfig"],
     visibility: [":__subpackages__"],
 }
@@ -922,9 +870,7 @@
 // Thread network
 aconfig_declarations {
     name: "com.android.net.thread.platform.flags-aconfig",
-    exportable: true,
     package: "com.android.net.thread.platform.flags",
-    container: "system",
     srcs: ["core/java/android/net/thread/flags.aconfig"],
 }
 
@@ -945,7 +891,6 @@
 aconfig_declarations {
     name: "android.media.playback.flags-aconfig",
     package: "com.android.media.playback.flags",
-    container: "system",
     srcs: ["media/jni/playback_flags.aconfig"],
 }
 
@@ -964,7 +909,6 @@
 aconfig_declarations {
     name: "android.net.vcn.flags-aconfig",
     package: "android.net.vcn",
-    container: "system",
     srcs: ["core/java/android/net/vcn/*.aconfig"],
 }
 
@@ -978,7 +922,6 @@
 aconfig_declarations {
     name: "device_policy_aconfig_flags",
     package: "android.app.admin.flags",
-    container: "system",
     srcs: [
         "core/java/android/app/admin/flags/flags.aconfig",
     ],
@@ -1006,7 +949,6 @@
 aconfig_declarations {
     name: "android.service.chooser.flags-aconfig",
     package: "android.service.chooser",
-    container: "system",
     srcs: ["core/java/android/service/chooser/flags.aconfig"],
 }
 
@@ -1025,8 +967,6 @@
 aconfig_declarations {
     name: "framework-jobscheduler-job.flags-aconfig",
     package: "android.app.job",
-    container: "system",
-    exportable: true,
     srcs: ["apex/jobscheduler/framework/aconfig/job.aconfig"],
 }
 
@@ -1040,7 +980,6 @@
 aconfig_declarations {
     name: "android.service.dreams.flags-aconfig",
     package: "android.service.dreams",
-    container: "system",
     srcs: ["core/java/android/service/dreams/flags.aconfig"],
 }
 
@@ -1081,7 +1020,6 @@
 aconfig_declarations {
     name: "android.app.contextualsearch.flags-aconfig",
     package: "android.app.contextualsearch.flags",
-    container: "system",
     srcs: ["core/java/android/app/contextualsearch/flags.aconfig"],
 }
 
@@ -1094,9 +1032,7 @@
 // Smartspace
 aconfig_declarations {
     name: "android.app.smartspace.flags-aconfig",
-    exportable: true,
     package: "android.app.smartspace.flags",
-    container: "system",
     srcs: ["core/java/android/app/smartspace/flags.aconfig"],
 }
 
@@ -1117,7 +1053,6 @@
 aconfig_declarations {
     name: "android.view.contentcapture.flags-aconfig",
     package: "android.view.contentcapture.flags",
-    container: "system",
     srcs: ["core/java/android/view/contentcapture/flags/*.aconfig"],
 }
 
@@ -1130,9 +1065,7 @@
 // USB
 aconfig_declarations {
     name: "android.hardware.usb.flags-aconfig",
-    exportable: true,
     package: "android.hardware.usb.flags",
-    container: "system",
     srcs: ["core/java/android/hardware/usb/flags/*.aconfig"],
 }
 
@@ -1153,7 +1086,6 @@
 aconfig_declarations {
     name: "android.tracing.flags-aconfig",
     package: "android.tracing",
-    container: "system",
     srcs: ["core/java/android/tracing/flags.aconfig"],
 }
 
@@ -1172,7 +1104,6 @@
 aconfig_declarations {
     name: "android.appwidget.flags-aconfig",
     package: "android.appwidget.flags",
-    container: "system",
     srcs: ["core/java/android/appwidget/flags.aconfig"],
 }
 
@@ -1186,7 +1117,6 @@
 aconfig_declarations {
     name: "android.server.app.flags-aconfig",
     package: "android.server.app",
-    container: "system",
     srcs: ["services/core/java/com/android/server/app/flags.aconfig"],
 }
 
@@ -1200,7 +1130,6 @@
 aconfig_declarations {
     name: "android.webkit.flags-aconfig",
     package: "android.webkit",
-    container: "system",
     srcs: [
         "core/java/android/webkit/*.aconfig",
         "services/core/java/com/android/server/webkit/*.aconfig",
@@ -1216,9 +1145,7 @@
 // Provider
 aconfig_declarations {
     name: "android.provider.flags-aconfig",
-    exportable: true,
     package: "android.provider",
-    container: "system",
     srcs: ["core/java/android/provider/*.aconfig"],
 }
 
@@ -1238,9 +1165,7 @@
 // Speech
 aconfig_declarations {
     name: "android.speech.flags-aconfig",
-    exportable: true,
     package: "android.speech.flags",
-    container: "system",
     srcs: ["core/java/android/speech/flags/*.aconfig"],
 }
 
@@ -1260,9 +1185,7 @@
 // Content
 aconfig_declarations {
     name: "android.content.flags-aconfig",
-    exportable: true,
     package: "android.content.flags",
-    container: "system",
     srcs: ["core/java/android/content/flags/flags.aconfig"],
 }
 
@@ -1276,7 +1199,6 @@
 aconfig_declarations {
     name: "android.adaptiveauth.flags-aconfig",
     package: "android.adaptiveauth",
-    container: "system",
     srcs: ["core/java/android/adaptiveauth/*.aconfig"],
 }
 
@@ -1289,9 +1211,7 @@
 // CrashRecovery Module
 aconfig_declarations {
     name: "android.crashrecovery.flags-aconfig",
-    exportable: true,
     package: "android.crashrecovery.flags",
-    container: "system",
     srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"],
 }
 
@@ -1312,7 +1232,6 @@
 aconfig_declarations {
     name: "android.net.wifi.flags-aconfig",
     package: "android.net.wifi.flags",
-    container: "system",
     srcs: ["wifi/*.aconfig"],
 }
 
@@ -1330,9 +1249,7 @@
 // Wearable Sensing
 aconfig_declarations {
     name: "android.app.wearable.flags-aconfig",
-    exportable: true,
     package: "android.app.wearable",
-    container: "system",
     srcs: ["core/java/android/app/wearable/*.aconfig"],
 }
 
@@ -1345,7 +1262,6 @@
 aconfig_declarations {
     name: "com.android.internal.pm.pkg.component.flags-aconfig",
     package: "com.android.internal.pm.pkg.component.flags",
-    container: "system",
     srcs: ["core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig"],
 }
 
@@ -1366,7 +1282,6 @@
 aconfig_declarations {
     name: "android.systemserver.flags-aconfig",
     package: "android.server",
-    container: "system",
     srcs: ["services/java/com/android/server/flags.aconfig"],
 }
 
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 6871762..762e2af 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -366,7 +366,7 @@
             mRunner.pauseTiming();
             Log.i(TAG, "Stopping timer");
 
-            stopUser(userId, /* force */true);
+            stopUser(userId);
             mRunner.resumeTimingForNextIteration();
         }
 
@@ -429,7 +429,7 @@
 
             mRunner.pauseTiming();
             Log.i(TAG, "Stopping timer");
-            stopUser(userId, /* force */true);
+            stopUser(userId);
             mRunner.resumeTimingForNextIteration();
         }
 
@@ -545,7 +545,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             switchUserNoCheck(currentUserId);
-            stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+            stopUserAfterWaitingForBroadcastIdle(userId);
             attestFalse("Failed to stop user " + userId, mAm.isUserRunning(userId));
             mRunner.resumeTimingForNextIteration();
         }
@@ -571,7 +571,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             switchUserNoCheck(startUser);
-            stopUserAfterWaitingForBroadcastIdle(testUser, true);
+            stopUserAfterWaitingForBroadcastIdle(testUser);
             attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
             mRunner.resumeTimingForNextIteration();
         }
@@ -660,7 +660,7 @@
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
-            stopUser(userId, false);
+            stopUser(userId);
 
             mRunner.pauseTiming();
             Log.i(TAG, "Stopping timer");
@@ -685,7 +685,7 @@
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
-            stopUser(userId, false);
+            stopUser(userId);
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
@@ -883,7 +883,7 @@
             final int userId = createManagedProfile();
             // Start the profile initially, then stop it. Similar to setQuietModeEnabled.
             startUserInBackgroundAndWaitForUnlock(userId);
-            stopUserAfterWaitingForBroadcastIdle(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId);
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -905,7 +905,7 @@
         startUserInBackgroundAndWaitForUnlock(userId);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            stopUserAfterWaitingForBroadcastIdle(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId);
             mRunner.resumeTiming();
             Log.d(TAG, "Starting timer");
 
@@ -987,7 +987,7 @@
             installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
             startUserInBackgroundAndWaitForUnlock(userId);
             startApp(userId, DUMMY_PACKAGE_NAME);
-            stopUserAfterWaitingForBroadcastIdle(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId);
             SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
@@ -1019,7 +1019,7 @@
             installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
             startUserInBackgroundAndWaitForUnlock(userId);
             startApp(userId, DUMMY_PACKAGE_NAME);
-            stopUserAfterWaitingForBroadcastIdle(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId);
             SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
             mRunner.resumeTiming();
             Log.d(TAG, "Starting timer");
@@ -1144,7 +1144,7 @@
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
-            stopUser(userId, true);
+            stopUser(userId);
 
             mRunner.pauseTiming();
             Log.i(TAG, "Stopping timer");
@@ -1168,7 +1168,7 @@
             mRunner.resumeTiming();
             Log.d(TAG, "Starting timer");
 
-            stopUser(userId, true);
+            stopUser(userId);
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
@@ -1294,15 +1294,15 @@
      * Do not call this method while timing is on. i.e. between mRunner.resumeTiming() and
      * mRunner.pauseTiming(). Otherwise it would cause the test results to be spiky.
      */
-    private void stopUserAfterWaitingForBroadcastIdle(int userId, boolean force)
+    private void stopUserAfterWaitingForBroadcastIdle(int userId)
             throws RemoteException {
         waitForBroadcastIdle();
-        stopUser(userId, force);
+        stopUser(userId);
     }
 
-    private void stopUser(int userId, boolean force) throws RemoteException {
+    private void stopUser(int userId) throws RemoteException {
         final CountDownLatch latch = new CountDownLatch(1);
-        mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
+        mIam.stopUserWithCallback(userId, new IStopUserCallback.Stub() {
             @Override
             public void userStopped(int userId) throws RemoteException {
                 latch.countDown();
@@ -1352,7 +1352,7 @@
         attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
 
         if (stopNewUser) {
-            stopUserAfterWaitingForBroadcastIdle(testUser, true);
+            stopUserAfterWaitingForBroadcastIdle(testUser);
             attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
         }
 
@@ -1471,7 +1471,7 @@
     }
 
     private void removeUser(int userId) throws RemoteException {
-        stopUserAfterWaitingForBroadcastIdle(userId, true);
+        stopUserAfterWaitingForBroadcastIdle(userId);
         try {
             ShellHelper.runShellCommandWithTimeout("pm remove-user -w " + userId,
                     TIMEOUT_IN_SECOND);
@@ -1512,7 +1512,7 @@
 
             final boolean preStartComplete = mIam.startUserInBackgroundWithListener(userId,
                     preWaiter) && preWaiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
-            stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+            stopUserAfterWaitingForBroadcastIdle(userId);
 
             assertTrue("Pre start was not performed for user" + userId, preStartComplete);
         }
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 80db264..2c1a853 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -1,5 +1,4 @@
 package: "android.app.job"
-container: "system"
 
 flag {
     name: "enforce_minimum_time_windows"
diff --git a/apex/jobscheduler/framework/java/android/app/job/OWNERS b/apex/jobscheduler/framework/java/android/app/job/OWNERS
index b4a45f5..0b1e559 100644
--- a/apex/jobscheduler/framework/java/android/app/job/OWNERS
+++ b/apex/jobscheduler/framework/java/android/app/job/OWNERS
@@ -4,4 +4,3 @@
 omakoto@google.com
 ctate@android.com
 ctate@google.com
-kwekua@google.com
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index ace56d4..06c7d64 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -24,6 +24,7 @@
         "app-compat-annotations",
         "error_prone_annotations",
         "framework",
+        "keepanno-annotations",
         "services.core",
         "unsupportedappusage",
     ],
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index f9c8e0b..b982d12 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1795,6 +1795,10 @@
 
         mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
         mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
+        if (mStartUserBeforeScheduledAlarms) {
+            mUserWakeupStore = new UserWakeupStore();
+            mUserWakeupStore.init();
+        }
         if (mUseFrozenStateToDropListenerAlarms) {
             final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
                 final int size = frozenStates.length;
@@ -1913,10 +1917,6 @@
                 Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
             }
         }
-        if (mStartUserBeforeScheduledAlarms) {
-            mUserWakeupStore = new UserWakeupStore();
-            mUserWakeupStore.init();
-        }
         publishLocalService(AlarmManagerInternal.class, new LocalService());
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
@@ -3863,7 +3863,7 @@
         long nextNonWakeup = 0;
         if (mAlarmStore.size() > 0) {
             long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
-            if (mStartUserBeforeScheduledAlarms) {
+            if (mStartUserBeforeScheduledAlarms && mUserWakeupStore != null) {
                 final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime();
                 if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) {
                     firstWakeup = firstUserWakeup;
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 19bc716..613678b 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -130,6 +130,8 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.usage.AppIdleHistory.AppUsageHistory;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
 
 import libcore.util.EmptyArray;
 
@@ -588,6 +590,8 @@
         }
     }
 
+    // This constructor is reflectively invoked from framework code in AppStandbyInternal.
+    @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
     public AppStandbyController(Context context) {
         this(new Injector(context, AppSchedulingModuleThread.get().getLooper()));
     }
diff --git a/apex/jobscheduler/service/jni/Android.bp b/apex/jobscheduler/service/jni/Android.bp
index 34a1fa2..e8acff7 100644
--- a/apex/jobscheduler/service/jni/Android.bp
+++ b/apex/jobscheduler/service/jni/Android.bp
@@ -28,4 +28,8 @@
         "liblog",
         "libbase",
     ],
+    visibility: [
+        "//frameworks/base/apex:__subpackages__",
+        "//visibility:any_system_partition",
+    ],
 }
diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING
index 7a449ef..af54a2d 100644
--- a/cmds/locksettings/TEST_MAPPING
+++ b/cmds/locksettings/TEST_MAPPING
@@ -11,5 +11,10 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsDevicePolicyManagerTestCases_LockSettings_NoFlakes"
+        }
     ]
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index b19c3ab..13d5e03 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9877,7 +9877,7 @@
     ctor public ObservingDevicePresenceRequest.Builder();
     method @NonNull public android.companion.ObservingDevicePresenceRequest build();
     method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
   }
 
   public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f36aeab..3b988e1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -157,7 +157,7 @@
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -1765,19 +1765,22 @@
   public final class InputManager {
     method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
-    method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
-    method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+    method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
     method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
     method public int getMousePointerSpeed();
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
-    method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method public void removeUniqueIdAssociation(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
   }
 
   public class InputSettings {
+    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
+    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
+    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
     field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
   }
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
index b9cf29c..de4e607 100644
--- a/core/java/android/adaptiveauth/flags.aconfig
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.adaptiveauth"
-container: "system"
 
 flag {
   name: "enable_adaptive_auth"
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7725561..0dab3de 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5071,7 +5071,7 @@
      * <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
      * user before starting a new one, this method does not stop the previous user running in
      * background in the display, and it will return {@code false} in this case. It's up to the
-     * caller to call {@link #stopUser(int, boolean)} before starting a new user.
+     * caller to call {@link #stopUser(int)} before starting a new user.
      *
      * @param userId user to be started in the display. It will return {@code false} if the user is
      * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
@@ -5281,15 +5281,16 @@
      *
      * @hide
      */
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
     @TestApi
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-    public boolean stopUser(@UserIdInt int userId, boolean force) {
+    public boolean stopUser(@UserIdInt int userId) {
         if (userId == UserHandle.USER_SYSTEM) {
             return false;
         }
         try {
-            return USER_OP_SUCCESS == getService().stopUser(
-                    userId, force, /* callback= */ null);
+            return USER_OP_SUCCESS == getService().stopUserWithCallback(
+                    userId, /* callback= */ null);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e094ac6..9ea55f5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2205,19 +2205,6 @@
     }
 
     /**
-     * Sets background activity launch logic won't use pending intent creator foreground state.
-     *
-     * @hide
-     * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead
-     */
-    @Deprecated
-    public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) {
-        mPendingIntentCreatorBackgroundActivityStartMode = ignore
-                ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
-        return this;
-    }
-
-    /**
      * Allow a {@link PendingIntent} to use the privilege of its creator to start background
      * activities.
      *
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e53bd39..3575545 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1082,6 +1082,8 @@
         public boolean managed;
         public boolean mallocInfo;
         public boolean runGc;
+        // compression format to dump bitmaps, null if no bitmaps to be dumped
+        public String dumpBitmaps;
         String path;
         ParcelFileDescriptor fd;
         RemoteCallback finishCallback;
@@ -1486,11 +1488,12 @@
         }
 
         @Override
-        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
-                ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String dumpBitmaps,
+                String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
             DumpHeapData dhd = new DumpHeapData();
             dhd.managed = managed;
             dhd.mallocInfo = mallocInfo;
+            dhd.dumpBitmaps = dumpBitmaps;
             dhd.runGc = runGc;
             dhd.path = path;
             try {
@@ -6859,6 +6862,9 @@
             System.runFinalization();
             System.gc();
         }
+        if (dhd.dumpBitmaps != null) {
+            Bitmap.dumpAll(dhd.dumpBitmaps);
+        }
         try (ParcelFileDescriptor fd = dhd.fd) {
             if (dhd.managed) {
                 Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
@@ -6886,6 +6892,9 @@
         if (dhd.finishCallback != null) {
             dhd.finishCallback.sendResult(null);
         }
+        if (dhd.dumpBitmaps != null) {
+            Bitmap.dumpAll(null); // clear dump
+        }
     }
 
     final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ee0225f..e3380e0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -21,12 +21,14 @@
 import static android.os.StrictMode.vmIncorrectContextUseEnabled;
 import static android.view.WindowManager.LayoutParams.WindowType;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UiContext;
+import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
@@ -2288,7 +2290,35 @@
             Log.v(TAG, "Treating renounced permission " + permission + " as denied");
             return PERMISSION_DENIED;
         }
-        return PermissionManager.checkPermission(permission, pid, uid, getDeviceId());
+
+        // When checking a device-aware permission on a remote device, if the permission is CAMERA
+        // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
+        // device doesn't have capability fall back to checking permission on the default device.
+        // Note: we only perform permission check redirection when the device id is not explicitly
+        // set in the context.
+        int deviceId = getDeviceId();
+        if (deviceId != Context.DEVICE_ID_DEFAULT
+                && !mIsExplicitDeviceId
+                && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
+            VirtualDeviceManager virtualDeviceManager =
+                    getSystemService(VirtualDeviceManager.class);
+            VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+            if (virtualDevice != null) {
+                if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+                                && !virtualDevice.hasCustomAudioInputSupport())
+                        || (Objects.equals(permission, Manifest.permission.CAMERA)
+                                && !virtualDevice.hasCustomCameraSupport())) {
+                    deviceId = Context.DEVICE_ID_DEFAULT;
+                }
+            } else {
+                Slog.e(
+                        TAG,
+                        "virtualDevice is not found when device id is not default. deviceId = "
+                                + deviceId);
+            }
+        }
+
+        return PermissionManager.checkPermission(permission, pid, uid, deviceId);
     }
 
     /** @hide */
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 85611e8..dca164d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -394,7 +394,7 @@
     oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
     // Cause the specified process to dump the specified heap.
     boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
-            boolean runGc, in String path, in ParcelFileDescriptor fd,
+            boolean runGc, in String dumpBitmaps, in String path, in ParcelFileDescriptor fd,
             in RemoteCallback finishCallback);
     @UnsupportedAppUsage
     boolean isUserRunning(int userid, int flags);
@@ -452,12 +452,14 @@
             in IBinder resultTo, in String resultWho, int requestCode, int flags,
             in ProfilerInfo profilerInfo, in Bundle options, int userId);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    int stopUser(int userid, boolean force, in IStopUserCallback callback);
+    int stopUser(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
+    int stopUserWithCallback(int userid, in IStopUserCallback callback);
+    int stopUserExceptCertainProfiles(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
     /**
-     * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+     * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, IStopUserCallback)}
      * for details.
      */
-    int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+    int stopUserWithDelayedLocking(int userid, in IStopUserCallback callback);
 
     @UnsupportedAppUsage
     void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
@@ -499,6 +501,7 @@
             in String shareDescription);
 
     void requestInteractiveBugReport();
+    void requestBugReportWithExtraAttachment(in Uri extraAttachment);
     void requestFullBugReport();
     void requestRemoteBugReport(long nonce);
     boolean launchBugReportHandlerApp();
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 251e4e8..a64261a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -119,7 +119,8 @@
     void scheduleSuicide();
     void dispatchPackageBroadcast(int cmd, in String[] packages);
     void scheduleCrash(in String msg, int typeId, in Bundle extras);
-    void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path,
+    void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc,
+            in String dumpBitmaps, in String path,
             in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
     void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
             in String[] args);
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index e4425ca..e751bd2 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
      namespace: "system_performance"
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index 8f71fc0..91b9761 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,4 +1,4 @@
 # Assign bugs to android-enterprise-triage@google.com
 ae-provisioning-reviews@google.com
-petuska@google.com #{LAST_RESORT_SUGGESTION}
+acjohnston@google.com #{LAST_RESORT_SUGGESTION}
 file:EnterprisePlatform_OWNERS
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 56fb4aa..fe2f764 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -2,7 +2,6 @@
 # proto-message: flag_declarations
 
 package: "android.app.admin.flags"
-container: "system"
 
 flag {
   name: "policy_engine_migration_v2_enabled"
diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig
index d29c5b5..5f3bb07 100644
--- a/core/java/android/app/background_install_control_manager.aconfig
+++ b/core/java/android/app/background_install_control_manager.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
      namespace: "preload_safety"
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 3385b2b..5ab0762 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.app.contextualsearch.flags"
-container: "system"
 
 flag {
   name: "enable_service"
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
index ea494f4..0d7bf65 100644
--- a/core/java/android/app/grammatical_inflection_manager.aconfig
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
     name: "system_terms_of_address_enabled"
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
index 9a64519..dbf3173 100644
--- a/core/java/android/app/multitasking.aconfig
+++ b/core/java/android/app/multitasking.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
     name: "enable_pip_ui_state_callback_on_entering"
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
index e7b02a7..88f386f 100644
--- a/core/java/android/app/network-policy.aconfig
+++ b/core/java/android/app/network-policy.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
      namespace: "backstage_power"
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 0082732..0214d40 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
   name: "modes_api"
diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
index 8b6441a..dd9210f 100644
--- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
+++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
@@ -1,5 +1,4 @@
 package: "android.app.ondeviceintelligence.flags"
-container: "system"
 
 flag {
     name: "enable_on_device_intelligence"
diff --git a/core/java/android/app/pinner-client.aconfig b/core/java/android/app/pinner-client.aconfig
index 696fd38..0f7fa14 100644
--- a/core/java/android/app/pinner-client.aconfig
+++ b/core/java/android/app/pinner-client.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
      namespace: "system_performance"
diff --git a/core/java/android/app/smartspace/flags.aconfig b/core/java/android/app/smartspace/flags.aconfig
index df71924..e90ba67 100644
--- a/core/java/android/app/smartspace/flags.aconfig
+++ b/core/java/android/app/smartspace/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.app.smartspace.flags"
-container: "system"
 
 flag {
   name: "remote_views"
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 9f44a4d..27a38cc 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -1,5 +1,4 @@
 package: "android.app"
-container: "system"
 
 flag {
      namespace: "systemui"
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index c7b168a..9a2d2e5 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.app.usage"
-container: "system"
 
 flag {
     name: "user_interaction_type_api"
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
index b68bafe..d1d7b5d 100644
--- a/core/java/android/app/wearable/flags.aconfig
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.app.wearable"
-container: "system"
 
 flag {
     name: "enable_unsupported_operation_status_code"
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 3bcc7c7..4511954 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.appwidget.flags"
-container: "system"
 
 flag {
   name: "generated_previews"
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
index f1d594e..11ea735 100644
--- a/core/java/android/companion/ObservingDevicePresenceRequest.java
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -183,7 +183,11 @@
          * @param uuid The ParcelUuid for observing device presence.
          */
         @NonNull
-        @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE,
+                android.Manifest.permission.BLUETOOTH_CONNECT,
+                android.Manifest.permission.BLUETOOTH_SCAN
+        })
         public Builder setUuid(@NonNull ParcelUuid uuid) {
             checkNotUsed();
             this.mUuid = uuid;
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 8458857..ecc5e1b 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.companion"
-container: "system"
 
 flag {
     name: "new_association_builder"
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 18c81a2..e6649df 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -8,7 +8,6 @@
 # instead.
 
 package: "android.companion.virtual.flags"
-container: "system"
 
 flag {
   name: "enable_native_vdm"
@@ -117,3 +116,14 @@
   description: "Enable virtual stylus input"
   bug: "304829446"
 }
+
+flag {
+  name: "intercept_intents_before_applying_policy"
+  is_exported: true
+  namespace: "virtual_devices"
+  description: "Apply intent interception before applying activity policy"
+  bug: "333444131"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 006226e..2904e7c 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -14,7 +14,6 @@
 # limitations under the License.
 
 package: "android.companion.virtualdevice.flags"
-container: "system"
 
 flag {
      namespace: "virtual_devices"
diff --git a/core/java/android/content/flags/flags.aconfig b/core/java/android/content/flags/flags.aconfig
index aac04b3a..27bce5b 100644
--- a/core/java/android/content/flags/flags.aconfig
+++ b/core/java/android/content/flags/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.content.flags"
-container: "system"
 
 flag {
     name: "enable_bind_package_isolated_process"
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 9159929..1d4403c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -887,7 +887,7 @@
      * <p> Setting this property to true will enable the user's CE storage to remain unlocked when
      * the user is stopped using
      * {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
-     * boolean, IStopUserCallback)}.
+     * IStopUserCallback)}.
      *
      * <p> When this property is false, delayed locking may still be applicable at a global
      * level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 6158917..cde565b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.content.pm"
-container: "system"
 
 flag {
     name: "quarantined_enabled"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 0c0da31..4963a4f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -1,5 +1,4 @@
 package: "android.multiuser"
-container: "system"
 
 flag {
     name: "save_global_and_guest_restrictions_on_system_user_xml"
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 625d7cb..c7237ea 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -58,6 +58,16 @@
         synchronized (LOOKUP_TABLES_WRITE_LOCK) {
             putInto(
                     sLookupTables,
+                    /* scaleKey= */ 1.05f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] { 8.4f, 10.5f, 12.6f, 14.8f, 18.6f, 20.6f, 24.4f,   30f,  100})
+            );
+
+            putInto(
+                    sLookupTables,
                     /* scaleKey= */ 1.1f,
                     new FontScaleConverterImpl(
                             /* fromSp= */
@@ -78,6 +88,16 @@
 
             putInto(
                     sLookupTables,
+                    /* scaleKey= */ 1.2f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] { 9.6f,   12f, 14.4f, 17.2f, 20.4f, 22.4f, 25.6f,   30f,  100})
+            );
+
+            putInto(
+                    sLookupTables,
                     /* scaleKey= */ 1.3f,
                     new FontScaleConverterImpl(
                             /* fromSp= */
@@ -117,7 +137,7 @@
             );
         }
 
-        sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.02f;
+        sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.01f;
         if (sMinScaleBeforeCurvesApplied <= 1.0f) {
             throw new IllegalStateException(
                     "You should only apply non-linear scaling to font scales > 1"
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a475cc8..8f5c912 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.content.res"
-container: "system"
 
 flag {
     name: "default_locale"
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index d243575..d077329 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.credentials.flags"
-container: "system"
 
 flag {
     namespace: "credential_manager"
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 3073e25..7ecffaf 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.database.sqlite"
-container: "system"
 
 flag {
      name: "sqlite_apis_35"
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 4284ad0..9836eec 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.hardware.biometrics"
-container: "system"
 
 flag {
     name: "last_authentication_time"
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 12d3f94..e474603 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.hardware.devicestate.feature.flags"
-container: "system"
 
 flag {
     name: "device_state_property_api"
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
index 6c86108..1165e65 100644
--- a/core/java/android/hardware/flags/overlayproperties_flags.aconfig
+++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.hardware.flags"
-container: "system"
 
 flag {
     name: "overlayproperties_class_api"
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2816f77..1c37aa2 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -94,33 +94,8 @@
     // Keyboard layouts configuration.
     KeyboardLayout[] getKeyboardLayouts();
 
-    KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
     KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
 
-    String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
-
-    @EnforcePermission("SET_KEYBOARD_LAYOUT")
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
-    void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor);
-
-    String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
-    @EnforcePermission("SET_KEYBOARD_LAYOUT")
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
-    void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor);
-
-    @EnforcePermission("SET_KEYBOARD_LAYOUT")
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
-    void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor);
-
-    // New Keyboard layout config APIs
     KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
             in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
             in InputMethodSubtype imeSubtype);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a1242fb..f949158 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -473,22 +473,21 @@
     }
 
     /**
-     * Returns the descriptors of all supported keyboard layouts appropriate for the specified
-     * input device.
+     * Returns the descriptors of all supported keyboard layouts.
      * <p>
      * The input manager consults the built-in keyboard layouts as well as all keyboard layouts
      * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
      * </p>
      *
-     * @param device The input device to query.
      * @return The ids of all keyboard layouts which are supported by the specified input device.
      *
      * @hide
      */
     @TestApi
     @NonNull
-    public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) {
-        KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier());
+    @SuppressLint("UnflaggedApi")
+    public List<String> getKeyboardLayoutDescriptors() {
+        KeyboardLayout[] layouts = getKeyboardLayouts();
         List<String> res = new ArrayList<>();
         for (KeyboardLayout kl : layouts) {
             res.add(kl.getDescriptor());
@@ -511,33 +510,18 @@
     @TestApi
     @NonNull
     public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
-        KeyboardLayout[] layouts = getKeyboardLayouts();
-        for (KeyboardLayout kl : layouts) {
-            if (layoutDescriptor.equals(kl.getDescriptor())) {
-                return kl.getLayoutType();
-            }
-        }
-        return "";
+        KeyboardLayout layout = getKeyboardLayout(layoutDescriptor);
+        return layout == null ? "" : layout.getLayoutType();
     }
 
     /**
-     * Gets information about all supported keyboard layouts appropriate
-     * for a specific input device.
-     * <p>
-     * The input manager consults the built-in keyboard layouts as well
-     * as all keyboard layouts advertised by applications using a
-     * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
-     * </p>
-     *
-     * @return A list of all supported keyboard layouts for a specific
-     * input device.
-     *
+     * TODO(b/330517633): Cleanup the unsupported API
      * @hide
      */
     @NonNull
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             @NonNull InputDeviceIdentifier identifier) {
-        return mGlobal.getKeyboardLayoutsForInputDevice(identifier);
+        return new KeyboardLayout[0];
     }
 
     /**
@@ -549,6 +533,7 @@
      *
      * @hide
      */
+    @Nullable
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
         if (keyboardLayoutDescriptor == null) {
             throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
@@ -562,121 +547,45 @@
     }
 
     /**
-     * Gets the current keyboard layout descriptor for the specified input device.
-     *
-     * @param identifier Identifier for the input device
-     * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
-     *
+     * TODO(b/330517633): Cleanup the unsupported API
      * @hide
      */
-    @TestApi
     @Nullable
     public String getCurrentKeyboardLayoutForInputDevice(
             @NonNull InputDeviceIdentifier identifier) {
-        try {
-            return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
-     * Sets the current keyboard layout descriptor for the specified input device.
-     * <p>
-     * This method may have the side-effect of causing the input device in question to be
-     * reconfigured.
-     * </p>
-     *
-     * @param identifier The identifier for the input device.
-     * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
-     *
+     * TODO(b/330517633): Cleanup the unsupported API
      * @hide
      */
-    @TestApi
-    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
-            @NonNull String keyboardLayoutDescriptor) {
-        mGlobal.setCurrentKeyboardLayoutForInputDevice(identifier,
-                keyboardLayoutDescriptor);
-    }
+            @NonNull String keyboardLayoutDescriptor) {}
 
     /**
-     * Gets all keyboard layout descriptors that are enabled for the specified input device.
-     *
-     * @param identifier The identifier for the input device.
-     * @return The keyboard layout descriptors.
-     *
+     * TODO(b/330517633): Cleanup the unsupported API
      * @hide
      */
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
-        if (identifier == null) {
-            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
-        }
-
-        try {
-            return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return new String[0];
     }
 
     /**
-     * Adds the keyboard layout descriptor for the specified input device.
-     * <p>
-     * This method may have the side-effect of causing the input device in question to be
-     * reconfigured.
-     * </p>
-     *
-     * @param identifier The identifier for the input device.
-     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
-     *
+     * TODO(b/330517633): Cleanup the unsupported API
      * @hide
      */
-    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (identifier == null) {
-            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
-        }
-        if (keyboardLayoutDescriptor == null) {
-            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
-        }
-
-        try {
-            mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
     }
 
     /**
-     * Removes the keyboard layout descriptor for the specified input device.
-     * <p>
-     * This method may have the side-effect of causing the input device in question to be
-     * reconfigured.
-     * </p>
-     *
-     * @param identifier The identifier for the input device.
-     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
-     *
+     * TODO(b/330517633): Cleanup the unsupported API
      * @hide
      */
-    @TestApi
     @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
             @NonNull String keyboardLayoutDescriptor) {
-        if (identifier == null) {
-            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
-        }
-        if (keyboardLayoutDescriptor == null) {
-            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
-        }
-
-        try {
-            mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
     }
 
     /**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7c104a0..7b29666 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1068,36 +1068,21 @@
     }
 
     /**
-     * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier)
+     * TODO(b/330517633): Cleanup the unsupported API
      */
     @NonNull
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             @NonNull InputDeviceIdentifier identifier) {
-        try {
-            return mIm.getKeyboardLayoutsForInputDevice(identifier);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return new KeyboardLayout[0];
     }
 
     /**
-     * @see InputManager#setCurrentKeyboardLayoutForInputDevice
-     * (InputDeviceIdentifier, String)
+     * TODO(b/330517633): Cleanup the unsupported API
      */
-    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     public void setCurrentKeyboardLayoutForInputDevice(
             @NonNull InputDeviceIdentifier identifier,
-            @NonNull String keyboardLayoutDescriptor) {
-        Objects.requireNonNull(identifier, "identifier must not be null");
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-        try {
-            mIm.setCurrentKeyboardLayoutForInputDevice(identifier,
-                    keyboardLayoutDescriptor);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
+            @NonNull String keyboardLayoutDescriptor) {}
+
 
     /**
      * @see InputDevice#getSensorManager()
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 4328d9f..4c5ebe7 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -16,6 +16,9 @@
 
 package android.hardware.input;
 
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG;
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
@@ -23,6 +26,7 @@
 import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -465,6 +469,8 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
     public static int getAccessibilityBounceKeysThreshold(@NonNull Context context) {
         if (!isAccessibilityBounceKeysFeatureEnabled()) {
             return 0;
@@ -487,6 +493,8 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
     @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
     public static void setAccessibilityBounceKeysThreshold(@NonNull Context context,
             int thresholdTimeMillis) {
@@ -545,6 +553,8 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
     public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
         if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
             return 0;
@@ -567,6 +577,8 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
     @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
     public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
             int thresholdTimeMillis) {
@@ -614,6 +626,8 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
     public static boolean isAccessibilityStickyKeysEnabled(@NonNull Context context) {
         if (!isAccessibilityStickyKeysFeatureEnabled()) {
             return false;
@@ -635,6 +649,8 @@
      *
      * @hide
      */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
     @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
     public static void setAccessibilityStickyKeysEnabled(@NonNull Context context,
             boolean enabled) {
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ed536ce..9684e64 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.hardware.input"
-container: "system"
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_input_native/changes
 
diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig
index c9ab62d..d0d10c1 100644
--- a/core/java/android/hardware/radio/flags.aconfig
+++ b/core/java/android/hardware/radio/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.hardware.radio"
-container: "system"
 
 flag {
     name: "hd_radio_improved"
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 967fc42..fac02ce 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.hardware.usb.flags"
-container: "system"
 
 flag {
     name: "enable_usb_data_compliance_warning"
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 94df160..3dd746c 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.hardware.usb.flags"
-container: "system"
 
 flag {
     name: "enable_is_pd_compliant_api"
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 2cd7aeb..cbfc5d1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -56,6 +56,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -400,9 +401,14 @@
     private long mStylusHwSessionsTimeout = STYLUS_HANDWRITING_IDLE_TIMEOUT_MS;
     private Runnable mStylusWindowIdleTimeoutRunnable;
     private long mStylusWindowIdleTimeoutForTest;
-    /** Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
+    /**
+     * Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
      **/
     private int mLastUsedToolType;
+    /**
+     * Tracks the ctrl+shift shortcut
+     **/
+    private boolean mUsingCtrlShiftShortcut = false;
 
     /**
      * Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -3612,7 +3618,8 @@
             // any KeyEvent keyDown should reset last toolType.
             updateEditorToolTypeInternal(MotionEvent.TOOL_TYPE_UNKNOWN);
         }
-        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
             final ExtractEditText eet = getExtractEditTextIfVisible();
             if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
                 return true;
@@ -3622,14 +3629,33 @@
                 return true;
             }
             return false;
-        } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+        } else if (keyCode == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
                 event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
             if (mDecorViewVisible && mWindowVisible) {
                 int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                 mPrivOps.switchKeyboardLayoutAsync(direction);
+                event.startTracking();
                 return true;
             }
         }
+
+        // Check if this may be a ctrl+shift shortcut
+        if (ctrlShiftShortcut()) {
+            if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+                    || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+                // Potentially Ctrl+Shift shortcut if Ctrl is currently pressed
+                mUsingCtrlShiftShortcut = KeyEvent.metaStateHasModifiers(
+                    event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON);
+            } else if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT
+                    || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
+                // Potentially Ctrl+Shift shortcut if Shift is currently pressed
+                mUsingCtrlShiftShortcut = KeyEvent.metaStateHasModifiers(
+                    event.getMetaState() & ~KeyEvent.META_CTRL_MASK, KeyEvent.META_SHIFT_ON);
+            } else {
+                mUsingCtrlShiftShortcut = false;
+            }
+        }
+
         return doMovementKey(keyCode, event, MOVEMENT_DOWN);
     }
 
@@ -3671,7 +3697,27 @@
      * them to perform navigation in the underlying application.
      */
     public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+        if (ctrlShiftShortcut()) {
+            if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+                    || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT
+                    || keyCode == KeyEvent.KEYCODE_CTRL_LEFT
+                    || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
+                if (mUsingCtrlShiftShortcut
+                        && event.hasNoModifiers()) {
+                    mUsingCtrlShiftShortcut = false;
+                    if (mDecorViewVisible && mWindowVisible) {
+                        // Move to the next IME
+                        switchToNextInputMethod(false /* onlyCurrentIme */);
+                        // TODO(b/332937629): Make the event stream consistent again
+                        return true;
+                    }
+                }
+            } else {
+                mUsingCtrlShiftShortcut = false;
+            }
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
             final ExtractEditText eet = getExtractEditTextIfVisible();
             if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
                 return true;
@@ -3679,7 +3725,12 @@
             if (event.isTracking() && !event.isCanceled()) {
                 return handleBack(true);
             }
+        } else if (keyCode == KeyEvent.KEYCODE_SPACE) {
+            if (event.isTracking() && !event.isCanceled()) {
+                return true;
+            }
         }
+
         return doMovementKey(keyCode, event, MOVEMENT_UP);
     }
 
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 048c50e..3544a69 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.net.platform.flags"
-container: "system"
 
 # This file contains aconfig flags used from platform code
 # Flags used for module APIs must be in aconfig files under each modules
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
index afb982b..ef798ad 100644
--- a/core/java/android/net/thread/flags.aconfig
+++ b/core/java/android/net/thread/flags.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.net.thread.platform.flags"
-container: "system"
 
 # This file contains aconfig flags used from platform code
 # Flags used for module APIs must be in aconfig files under each modules
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 15d671d..e64823a 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.net.vcn"
-container: "system"
 
 flag {
     name: "safe_mode_config"
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 2fe115f..5b711c9 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -25,8 +25,6 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
-import dalvik.annotation.optimization.CriticalNative;
-
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -80,7 +78,6 @@
     private native static void nativeDestroy(long ptr);
     @UnsupportedAppUsage
     private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
-    @CriticalNative
     private native static void nativeWake(long ptr);
     private native static boolean nativeIsPolling(long ptr);
     private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 8a17742..bb74a3e 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -1436,10 +1436,10 @@
      * @throws IOException if the recovery system service could not be contacted
      */
     private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
-        Log.i(TAG, String.format("<%s> is requesting LSFK", packageName));
+        Log.i(TAG, TextUtils.formatSimple("Package<%s> requesting LSKF", packageName));
         try {
             boolean validRequest = mService.requestLskf(packageName, sender);
-            Log.i(TAG, String.format("LSKF Request isValid = %b", validRequest));
+            Log.i(TAG, TextUtils.formatSimple("LSKF Request isValid = %b", validRequest));
             return validRequest;
         } catch (RemoteException | SecurityException e) {
             throw new IOException("could not request LSKF capture", e);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index fd955e2..f26a797 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,6 +1,5 @@
 package: "android.os"
 container: "system"
-container: "system"
 
 flag {
     name: "android_os_build_vanilla_ice_cream"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/core/java/android/os/storage/ICeStorageLockEventListener.java
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to core/java/android/os/storage/ICeStorageLockEventListener.java
index 5d2b0ad2..f16a7fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/core/java/android/os/storage/ICeStorageLockEventListener.java
@@ -14,7 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package android.os.storage;
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+
+/**
+ * Callback class for receiving CE storage lock events from StorageManagerService.
+ * @hide
+ */
+public interface ICeStorageLockEventListener {
+
+    /**
+     * Called when the CE storage corresponding to the userId is locked
+     */
+    void onStorageLocked(int userId);
+}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 6995ea8..8ba2fa4 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -16,10 +16,12 @@
 
 package android.os.storage;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.pm.UserInfo;
+import android.multiuser.Flags;
 import android.os.IInstalld;
 import android.os.IVold;
 import android.os.ParcelFileDescriptor;
@@ -201,4 +203,18 @@
      */
     public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
             String packageName) throws IOException;
+
+    /**
+     * Registers a {@link ICeStorageLockEventListener} for receiving CE storage lock events.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE)
+    public abstract void registerStorageLockEventListener(
+            @NonNull ICeStorageLockEventListener listener);
+
+    /**
+     * Unregisters the {@link ICeStorageLockEventListener} which was registered previously
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE)
+    public abstract void unregisterStorageLockEventListener(
+            @NonNull ICeStorageLockEventListener listener);
 }
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index eda755c..229d119 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.os.vibrator"
-container: "system"
 
 flag {
     namespace: "haptics"
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 3441244..fe3fa8c 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -240,6 +240,16 @@
     public static final String EXTRA_PERMISSION_USAGES =
             "android.permission.extra.PERMISSION_USAGES";
 
+    /**
+     * Specify what permissions are device aware. Only device aware permissions can be granted to
+     * a remote device.
+     * @hide
+     */
+    public static final Set<String> DEVICE_AWARE_PERMISSIONS =
+            Flags.deviceAwarePermissionsEnabled()
+                    ? Set.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+                    : Collections.emptySet();
+
     private final @NonNull Context mContext;
 
     private final IPackageManager mPackageManager;
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index c26f351..ec3c978 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.permission.flags"
-container: "system"
 
 flag {
   name: "device_aware_permission_apis_enabled"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b7d421a..dd93972 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8005,6 +8005,7 @@
          *
          * @hide
          */
+        @Readable
         public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
 
         /**
@@ -8016,6 +8017,7 @@
          *
          * @hide
          */
+        @Readable
         public static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
 
         /**
@@ -8025,6 +8027,7 @@
          *
          * @hide
          */
+        @Readable
         public static final String ACCESSIBILITY_STICKY_KEYS = "accessibility_sticky_keys";
 
         /**
@@ -10977,7 +10980,7 @@
                 "biometric_debug_enabled";
 
         /**
-         * Whether or not virtual sensors are enabled.
+         * Whether or not both fingerprint and face virtual sensors are enabled.
          * @hide
          */
         @TestApi
@@ -10985,6 +10988,22 @@
         public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled";
 
         /**
+         * Whether or not fingerprint virtual sensors are enabled.
+         * @hide
+         */
+        @FlaggedApi("com.android.server.biometrics.face_vhal_feature")
+        public static final String BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED =
+                "biometric_fingerprint_virtual_enabled";
+
+        /**
+         * Whether or not face virtual sensors are enabled.
+         * @hide
+         */
+        @FlaggedApi("com.android.server.biometrics.face_vhal_feature")
+        public static final String BIOMETRIC_FACE_VIRTUAL_ENABLED =
+                "biometric_face_virtual_enabled";
+
+        /**
          * Whether or not biometric is allowed on Keyguard.
          * @hide
          */
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 77353c2..d0cef83 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.provider"
-container: "system"
 
 flag {
     name: "a11y_standalone_fab_enabled"
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 02e787b..7f5b550 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.security"
-container: "system"
 
 flag {
     name: "certificate_transparency_configuration"
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index c7d951b..548f8aa 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.security"
-container: "system"
 
 flag {
     name: "extend_ecm_to_all_settings"
diff --git a/core/java/android/service/appprediction/flags/flags.aconfig b/core/java/android/service/appprediction/flags/flags.aconfig
index 953bc44..7f9764e 100644
--- a/core/java/android/service/appprediction/flags/flags.aconfig
+++ b/core/java/android/service/appprediction/flags/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.appprediction.flags"
-container: "system"
 
 flag {
   name: "service_features_api"
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index d6425c3..a3eff3b 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.chooser"
-container: "system"
 
 flag {
   name: "chooser_album_text"
diff --git a/core/java/android/service/controls/flags/flags.aconfig b/core/java/android/service/controls/flags/flags.aconfig
index 6f3a67d..197f1bc 100644
--- a/core/java/android/service/controls/flags/flags.aconfig
+++ b/core/java/android/service/controls/flags/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.controls.flags"
-container: "system"
 
 flag {
     name: "home_panel_dream"
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 2f45f34..cca4937 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.dreams"
-container: "system"
 
 flag {
   name: "dream_overlay_host"
@@ -11,7 +10,7 @@
 
 flag {
   name: "dream_handles_confirm_keys"
-  namespace: "dreams"
+  namespace: "communal"
   description: "This flag enables dreams processing confirm keys to show the bouncer or dismiss "
        "the keyguard"
   bug: "326975875"
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index b0c55a9..35cd3ed 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -1,6 +1,5 @@
 package: "android.service.notification"
 container: "system"
-container: "system"
 
 flag {
   name: "ranking_update_ashmem"
diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig
index 357cb47..1ae7d8c 100644
--- a/core/java/android/service/voice/flags/flags.aconfig
+++ b/core/java/android/service/voice/flags/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.voice.flags"
-container: "system"
 
 flag {
     name: "allow_training_data_egress_from_hds"
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0fc51e7..582c90f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,12 +27,15 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
 import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
+import static com.android.window.flags.Flags.offloadColorExtraction;
 
 import android.animation.AnimationHandler;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -832,6 +835,15 @@
         }
 
         /**
+         * Called when the dim amount of the wallpaper changed. This can be used to recompute the
+         * wallpaper colors based on the new dim, and call {@link #notifyColorsChanged()}.
+         * @hide
+         */
+        @FlaggedApi(FLAG_OFFLOAD_COLOR_EXTRACTION)
+        public void onDimAmountChanged(float dimAmount) {
+        }
+
+        /**
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
@@ -1043,6 +1055,10 @@
             }
 
             mPreviousWallpaperDimAmount = mWallpaperDimAmount;
+
+            // after the dim changes, allow colors to be immediately recomputed
+            mLastColorInvalidation = 0;
+            if (offloadColorExtraction()) onDimAmountChanged(mWallpaperDimAmount);
         }
 
         /**
diff --git a/core/java/android/speech/flags/speech_flags.aconfig b/core/java/android/speech/flags/speech_flags.aconfig
index 2a42357..fa33592 100644
--- a/core/java/android/speech/flags/speech_flags.aconfig
+++ b/core/java/android/speech/flags/speech_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.speech.flags"
-container: "system"
 
 flag {
     name: "multilang_extra_launch"
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 44fa186..24035af 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.text.flags"
-container: "system"
 
 flag {
   name: "vendor_custom_locale_fallback"
@@ -160,3 +159,14 @@
   description: "Feature flag for showing error message when user tries stylus handwriting on a text field which doesn't support it"
   bug: "297962571"
 }
+
+flag {
+  name: "fix_font_update_failure"
+  namespace: "text"
+  description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
+  is_fixed_read_only: true
+  bug: "331717791"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index c50c384..1815f14 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.tracing"
-container: "system"
 
 flag {
     name: "perfetto_transition_tracing"
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
index d0c719b..c33fa7d 100644
--- a/core/java/android/tracing/perfetto/DataSource.java
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import dalvik.annotation.optimization.CriticalNative;
+
 /**
  * Templated base class meant to be derived by embedders to create a custom data
  * source.
@@ -71,9 +73,24 @@
      * @param fun The tracing lambda that will be called with the tracing contexts of each active
      *            tracing instance.
      */
-    public final void trace(
-            TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
-        nativeTrace(mNativeObj, fun);
+    public final void trace(TraceFunction<TlsStateType, IncrementalStateType> fun) {
+        boolean startedIterator = nativePerfettoDsTraceIterateBegin(mNativeObj);
+
+        if (!startedIterator) {
+            return;
+        }
+
+        try {
+            do {
+                TracingContext<TlsStateType, IncrementalStateType> ctx =
+                        new TracingContext<>(mNativeObj);
+                fun.trace(ctx);
+
+                ctx.flush();
+            } while (nativePerfettoDsTraceIterateNext(mNativeObj));
+        } finally {
+            nativePerfettoDsTraceIterateBreak(mNativeObj);
+        }
     }
 
     /**
@@ -154,8 +171,6 @@
             long dataSourcePtr, int bufferExhaustedPolicy);
 
     private static native long nativeCreate(DataSource thiz, String name);
-    private static native void nativeTrace(
-            long nativeDataSourcePointer, TraceFunction traceFunction);
     private static native void nativeFlushAll(long nativeDataSourcePointer);
     private static native long nativeGetFinalizer();
 
@@ -163,4 +178,11 @@
             long dataSourcePtr, int dsInstanceIdx);
     private static native void nativeReleasePerfettoInstanceLocked(
             long dataSourcePtr, int dsInstanceIdx);
+
+    @CriticalNative
+    private static native boolean nativePerfettoDsTraceIterateBegin(long dataSourcePtr);
+    @CriticalNative
+    private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr);
+    @CriticalNative
+    private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr);
 }
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
index 62941df..13e663d 100644
--- a/core/java/android/tracing/perfetto/TraceFunction.java
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -16,19 +16,15 @@
 
 package android.tracing.perfetto;
 
-import java.io.IOException;
-
 /**
  * The interface for the trace function called from native on a trace call with a context.
  *
- * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
  * @param <TlsStateType> The type of the custom TLS state, if any is used.
  * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
  *
  * @hide
  */
-public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
-        TlsStateType, IncrementalStateType> {
+public interface TraceFunction<TlsStateType, IncrementalStateType> {
 
     /**
      * This function will be called synchronously (i.e., always before trace() returns) only if
@@ -38,6 +34,5 @@
      *
      * @param ctx the tracing context to trace for in the trace function.
      */
-    void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx)
-            throws IOException;
+    void trace(TracingContext<TlsStateType, IncrementalStateType> ctx);
 }
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index c1a61a7..060f964 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -24,26 +24,18 @@
 /**
  * Argument passed to the lambda function passed to Trace().
  *
- * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
  * @param <TlsStateType> The type of the custom TLS state, if any is used.
  * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
  *
  * @hide
  */
-public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
-        TlsStateType, IncrementalStateType> {
+public class TracingContext<TlsStateType, IncrementalStateType> {
 
-    private final long mContextPtr;
-    private final TlsStateType mTlsState;
-    private final IncrementalStateType mIncrementalState;
+    private final long mNativeDsPtr;
     private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
 
-    // Should only be created from the native side.
-    private TracingContext(long contextPtr, TlsStateType tlsState,
-            IncrementalStateType incrementalState) {
-        this.mContextPtr = contextPtr;
-        this.mTlsState = tlsState;
-        this.mIncrementalState = incrementalState;
+    TracingContext(long nativeDsPtr) {
+        this.mNativeDsPtr = nativeDsPtr;
     }
 
     /**
@@ -69,7 +61,7 @@
      * Stop timeout expires.
      */
     public void flush() {
-        nativeFlush(this, mContextPtr);
+        nativeFlush(mNativeDsPtr, getAndClearAllPendingTracePackets());
     }
 
     /**
@@ -80,7 +72,7 @@
      * @return The TlsState instance for the tracing thread and instance.
      */
     public TlsStateType getCustomTlsState() {
-        return this.mTlsState;
+        return (TlsStateType) nativeGetCustomTls(mNativeDsPtr);
     }
 
     /**
@@ -90,10 +82,9 @@
      * @return The current IncrementalState object instance.
      */
     public IncrementalStateType getIncrementalState() {
-        return this.mIncrementalState;
+        return (IncrementalStateType) nativeGetIncrementalState(mNativeDsPtr);
     }
 
-    // Called from native to get trace packets
     private byte[][] getAndClearAllPendingTracePackets() {
         byte[][] res = new byte[mTracePackets.size()][];
         for (int i = 0; i < mTracePackets.size(); i++) {
@@ -105,5 +96,7 @@
         return res;
     }
 
-    private static native void nativeFlush(TracingContext thiz, long ctxPointer);
+    private static native void nativeFlush(long dataSourcePtr, byte[][] packetData);
+    private static native Object nativeGetCustomTls(long nativeDsPtr);
+    private static native Object nativeGetIncrementalState(long nativeDsPtr);
 }
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b300022..6caf4d6 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -196,11 +196,11 @@
         if (!super.setControl(control, showTypes, hideTypes)) {
             return false;
         }
-        final boolean hasLeash = control != null && control.getLeash() != null;
-        if (!hasLeash && !mIsRequestedVisibleAwaitingLeash) {
+        if (control == null && !mIsRequestedVisibleAwaitingLeash) {
             mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
             removeSurface();
         }
+        final boolean hasLeash = control != null && control.getLeash() != null;
         if (hasLeash) {
             mIsRequestedVisibleAwaitingLeash = false;
         }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0ae3e59..56a24e4 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3783,6 +3783,13 @@
             throw new IllegalArgumentException(
                     "idBits must contain at least one pointer from this motion event");
         }
+        final int currentBits = getPointerIdBits();
+        if ((currentBits & idBits) != idBits) {
+            throw new IllegalArgumentException(
+                    "idBits must be a non-empty subset of the pointer IDs from this MotionEvent, "
+                            + "got idBits: "
+                            + String.format("0x%x", idBits) + " for " + this);
+        }
         MotionEvent event = obtain();
         event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
         return event;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index da6cd40..c5a4d67 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9587,6 +9587,8 @@
             }
             mRemoved = true;
             mOnBackInvokedDispatcher.detachFromWindow();
+            removeVrrMessages();
+
             if (mAdded) {
                 dispatchDetachedFromWindow();
             }
@@ -12551,8 +12553,8 @@
                 if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
                     mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
                         frameRateCategory, false).applyAsyncUnsafe();
-                    mLastPreferredFrameRateCategory = frameRateCategory;
                 }
+                mLastPreferredFrameRateCategory = frameRateCategory;
             }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate category", e);
@@ -12612,8 +12614,8 @@
                 if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
                     mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
                     mFrameRateCompatibility).applyAsyncUnsafe();
-                    mLastPreferredFrameRate = preferredFrameRate;
                 }
+                mLastPreferredFrameRate = preferredFrameRate;
             }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
@@ -12855,4 +12857,10 @@
             mHasIdledMessage = true;
         }
     }
+
+    private void removeVrrMessages() {
+        mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
+        mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
+        mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+    }
 }
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index f4aef22..eefc72b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.accessibility"
-container: "system"
 
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index 416a877..3c15518 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.contentcapture.flags"
-container: "system"
 
 flag {
     name: "run_on_background_thread_enabled"
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index b3bd92b..4de0f29 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.contentprotection.flags"
-container: "system"
 
 flag {
     name: "blocklist_update_enabled"
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index d0fe3e0..1047131 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.flags"
-container: "system"
 
 flag {
     name: "view_velocity_api"
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index 338037f..a7c4104 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.flags"
-container: "system"
 
 flag {
     namespace: "toolkit"
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index e8e02ec..c482f8b 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.flags"
-container: "system"
 
 flag {
     name: "enable_surface_native_alloc_registration_ro"
diff --git a/core/java/android/view/flags/window_insets.aconfig b/core/java/android/view/flags/window_insets.aconfig
index bedb7d5..bf6df5c 100644
--- a/core/java/android/view/flags/window_insets.aconfig
+++ b/core/java/android/view/flags/window_insets.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.flags"
-container: "system"
 
 flag {
     name: "customizable_window_headers"
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 4c3a290..0d19746 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.view.inputmethod"
-container: "system"
 
 flag {
     name: "refactor_insets_controller"
@@ -94,3 +93,15 @@
     bug: "322836622"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "ctrl_shift_shortcut"
+    namespace: "input_method"
+    description: "Ctrl+Shift shortcut to switch IMEs"
+    bug: "327198899"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index defe61e..2d834a8 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.webkit"
-container: "system"
 
 flag {
     name: "update_service_ipc_wrapper"
diff --git a/core/java/android/widget/flags/differential_motion_fling_flags.aconfig b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
index a0a391e..79cfe56 100644
--- a/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
+++ b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.widget.flags"
-container: "system"
 
 flag {
     namespace: "toolkit"
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index b530e71..e60fa15 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.widget.flags"
-container: "system"
 
 flag {
    name: "notif_linearlayout_optimized"
diff --git a/core/java/android/window/flags/accessibility.aconfig b/core/java/android/window/flags/accessibility.aconfig
index 733e3db..368c609 100644
--- a/core/java/android/window/flags/accessibility.aconfig
+++ b/core/java/android/window/flags/accessibility.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 flag {
   name: "do_not_check_intersection_when_non_magnifiable_window_transitions"
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 98ff3c6..fa0dab0 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 flag {
   name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index efe31ff..9524a6e 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 flag {
     name: "enable_scaled_resizing"
@@ -36,3 +35,17 @@
     description: "Enables a limit on the number of Tasks shown in Desktop Mode"
     bug: "332502912"
 }
+
+flag {
+    name: "enable_windowing_edge_drag_resize"
+    namespace: "lse_desktop_experience"
+    description: "Enables edge drag resizing for all input sources"
+    bug: "323383067"
+}
+
+flag {
+    name: "enable_desktop_windowing_taskbar_running_apps"
+    namespace: "lse_desktop_experience"
+    description: "Shows running apps in Desktop Mode Taskbar"
+    bug: "332504528"
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 33af486..94c72c6 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 flag {
     name: "bal_require_opt_in_by_pending_intent_creator"
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index aa92af2..edf90b5 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 flag {
     name: "always_update_wallpaper_permission"
@@ -21,4 +20,14 @@
   namespace: "systemui"
   description: "Prevent the system from sending consecutive onVisibilityChanged(false) events."
   bug: "285631818"
+}
+
+flag {
+  name: "offload_color_extraction"
+  namespace: "systemui"
+  description: "Let ImageWallpaper take care of its wallpaper color extraction, instead of system_server"
+  bug: "328791519"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 460df31..5c31048 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes
 
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index dd6b772a..e2efff3 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 flag {
   name: "nav_bar_transparent_by_default"
@@ -128,9 +127,28 @@
 }
 
 flag {
+  name: "fifo_priority_for_major_ui_processes"
+  namespace: "windowing_frontend"
+  description: "Use realtime priority for SystemUI and launcher"
+  bug: "288140556"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "insets_decoupled_configuration"
   namespace: "windowing_frontend"
   description: "Configuration decoupled from insets"
   bug: "151861875"
   is_fixed_read_only: true
+}
+
+flag {
+  name: "keyguard_appear_transition"
+  namespace: "windowing_frontend"
+  description: "Add transition when keyguard appears"
+  bug: "327970608"
+  is_fixed_read_only: true
+  metadata {
+      purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 80265ec..4b3d8e8 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.window.flags"
-container: "system"
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
 
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index a0c405e..ddb8ee0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -24,8 +24,10 @@
 import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
 import static com.android.internal.util.ArrayUtils.convertToLongArray;
 
+import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AlertDialog;
@@ -53,6 +55,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -328,11 +331,14 @@
         warningToast.show();
     }
 
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
     private AlertDialog createShortcutWarningDialog(int userId) {
         List<AccessibilityTarget> targets = getTargets(mContext, HARDWARE);
         if (targets.size() == 0) {
             return null;
         }
+        final AccessibilityManager am = mFrameworkObjectProvider
+                .getAccessibilityManagerInstance(mContext);
 
         // Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
         // Put "don't turn on" as the primary action.
@@ -360,10 +366,15 @@
                                 // to the Settings.
                                 final ComponentName configDefaultService =
                                         ComponentName.unflattenFromString(defaultService);
-                                Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                                        configDefaultService.flattenToString(),
-                                        userId);
+                                if (Flags.a11yQsShortcut()) {
+                                    am.enableShortcutsForTargets(true, HARDWARE,
+                                            Set.of(configDefaultService.flattenToString()), userId);
+                                } else {
+                                    Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                                            Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                                            configDefaultService.flattenToString(),
+                                            userId);
+                                }
                             }
                         })
                 .setPositiveButton(R.string.accessibility_shortcut_off,
@@ -373,13 +384,16 @@
                                             mContext,
                                             HARDWARE,
                                             userId);
-
-                            Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
-                                    userId);
-                            ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
-                                    mContext, targetServices, userId);
-
+                            if (Flags.a11yQsShortcut()) {
+                                am.enableShortcutsForTargets(
+                                        false, HARDWARE, targetServices, userId);
+                            } else {
+                                Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
+                                        userId);
+                                ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+                                        mContext, targetServices, userId);
+                            }
                             // If canceled, treat as if the dialog has never been shown
                             Settings.Secure.putIntForUser(mContext.getContentResolver(),
                                     Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ba1dffc..f088592 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -23,11 +23,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -35,6 +38,8 @@
 import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Set;
+
 /**
  * Abstract base class for creating various target related to accessibility service, accessibility
  * activity, and allowlisting features.
@@ -108,13 +113,21 @@
         }
     }
 
+    @SuppressLint("MissingPermission")
     @Override
     public void onCheckedChanged(boolean isChecked) {
         setShortcutEnabled(isChecked);
-        if (isChecked) {
-            optInValueToSettings(getContext(), getShortcutType(), getId());
+        if (Flags.a11yQsShortcut()) {
+            final AccessibilityManager am =
+                    getContext().getSystemService(AccessibilityManager.class);
+            am.enableShortcutsForTargets(
+                    isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
         } else {
-            optOutValueFromSettings(getContext(), getShortcutType(), getId());
+            if (isChecked) {
+                optInValueToSettings(getContext(), getShortcutType(), getId());
+            } else {
+                optOutValueFromSettings(getContext(), getShortcutType(), getId());
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 7535979..f9efb50 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -17,17 +17,11 @@
 package com.android.internal.accessibility.dialog;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
-import android.content.ComponentName;
 import android.content.Context;
-import android.widget.Toast;
 
-import com.android.internal.R;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 
@@ -47,30 +41,10 @@
 
     @Override
     public void onCheckedChanged(boolean isChecked) {
-        switch (getShortcutType()) {
-            case SOFTWARE:
-                onCheckedFromAccessibilityButton(isChecked);
-                return;
-            case HARDWARE:
-                super.onCheckedChanged(isChecked);
-                return;
-            default:
-                throw new IllegalStateException("Unexpected shortcut type");
-        }
-    }
-
-    private void onCheckedFromAccessibilityButton(boolean isChecked) {
-        setShortcutEnabled(isChecked);
-        final ComponentName componentName = ComponentName.unflattenFromString(getId());
-        setAccessibilityServiceState(getContext(), componentName, isChecked);
-
-        if (!isChecked) {
-            optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
-
-            final String warningText =
-                    getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
-                            getLabel());
-            Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
+        if (getShortcutType() == HARDWARE) {
+            super.onCheckedChanged(isChecked);
+        } else {
+            throw new IllegalStateException("Unexpected shortcut type");
         }
     }
 }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 6bd273b..4f55441 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -28,6 +28,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER;
+import static com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER;
 import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
 
 import android.annotation.Nullable;
@@ -190,7 +191,7 @@
                 .thenApplyAsync(targetResolveInfo -> {
                     if (isResolverActivityResolveInfo(targetResolveInfo)) {
                         launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
-                                callingUserId, targetUserId);
+                                callingUserId, targetUserId, false);
                     // When switching to the personal profile, automatically start the activity
                     } else if (className.equals(FORWARD_INTENT_TO_PARENT)) {
                         startActivityAsCaller(newIntent, targetUserId);
@@ -218,7 +219,7 @@
                 .thenAcceptAsync(targetResolveInfo -> {
                     if (isResolverActivityResolveInfo(targetResolveInfo)) {
                         launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
-                                callingUserId, targetUserId);
+                                callingUserId, targetUserId, true);
                     } else {
                         maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
                                 targetUserId);
@@ -490,7 +491,7 @@
     }
 
     private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className,
-            Intent newIntent, int callingUserId, int targetUserId) {
+            Intent newIntent, int callingUserId, int targetUserId, boolean singleTabOnly) {
         // When showing the intent resolver, instead of forwarding to the other profile,
         // we launch it in the current user and select the other tab. This fixes b/155874820.
         //
@@ -505,6 +506,9 @@
         sanitizeIntent(intentReceived);
         intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
         intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId));
+        if (singleTabOnly) {
+            intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
+        }
         startActivityAsCaller(intentReceived, null, false, userId);
         finish();
     }
diff --git a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
index 89db1cb..ea9abdb 100644
--- a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
+++ b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.internal.pm.pkg.component.flags"
-container: "system"
 
 flag {
     name: "enable_per_process_use_embedded_dex_attr"
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 9f3ce81..fca4e91 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -53,6 +53,7 @@
 import android.tracing.perfetto.Producer;
 import android.tracing.perfetto.TracingContext;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.LongArray;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
@@ -67,6 +68,7 @@
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -166,78 +168,92 @@
         }
 
         mDataSource.trace(ctx -> {
-            final ProtoOutputStream os = ctx.newTracePacket();
+            try {
+                final ProtoOutputStream os = ctx.newTracePacket();
 
-            os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
 
-            final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
-            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                if (pis.getFieldNumber() == (int) MESSAGES) {
-                    final long inMessageToken = pis.start(MESSAGES);
-                    final long outMessagesToken = os.start(MESSAGES);
-
-                    while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                        switch (pis.getFieldNumber()) {
-                            case (int) MessageData.MESSAGE_ID:
-                                os.write(MessageData.MESSAGE_ID,
-                                        pis.readLong(MessageData.MESSAGE_ID));
-                                break;
-                            case (int) MESSAGE:
-                                os.write(MESSAGE, pis.readString(MESSAGE));
-                                break;
-                            case (int) LEVEL:
-                                os.write(LEVEL, pis.readInt(LEVEL));
-                                break;
-                            case (int) GROUP_ID:
-                                os.write(GROUP_ID, pis.readInt(GROUP_ID));
-                                break;
-                            default:
-                                throw new RuntimeException(
-                                        "Unexpected field id " + pis.getFieldNumber());
-                        }
+                final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    if (pis.getFieldNumber() == (int) MESSAGES) {
+                        writeViewerConfigMessage(pis, os);
                     }
 
-                    pis.end(inMessageToken);
-                    os.end(outMessagesToken);
-                }
-
-                if (pis.getFieldNumber() == (int) GROUPS) {
-                    final long inGroupToken = pis.start(GROUPS);
-                    final long outGroupToken = os.start(GROUPS);
-
-                    while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                        switch (pis.getFieldNumber()) {
-                            case (int) ID:
-                                int id = pis.readInt(ID);
-                                os.write(ID, id);
-                                break;
-                            case (int) NAME:
-                                String name = pis.readString(NAME);
-                                os.write(NAME, name);
-                                break;
-                            case (int) TAG:
-                                String tag = pis.readString(TAG);
-                                os.write(TAG, tag);
-                                break;
-                            default:
-                                throw new RuntimeException(
-                                        "Unexpected field id " + pis.getFieldNumber());
-                        }
+                    if (pis.getFieldNumber() == (int) GROUPS) {
+                        writeViewerConfigGroup(pis, os);
                     }
-
-                    pis.end(inGroupToken);
-                    os.end(outGroupToken);
                 }
+
+                os.end(outProtologViewerConfigToken);
+
+                ctx.flush();
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
             }
-
-            os.end(outProtologViewerConfigToken);
-
-            ctx.flush();
         });
 
         mDataSource.flush();
     }
 
+    private static void writeViewerConfigGroup(
+            ProtoInputStream pis, ProtoOutputStream os) throws IOException {
+        final long inGroupToken = pis.start(GROUPS);
+        final long outGroupToken = os.start(GROUPS);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) ID:
+                    int id = pis.readInt(ID);
+                    os.write(ID, id);
+                    break;
+                case (int) NAME:
+                    String name = pis.readString(NAME);
+                    os.write(NAME, name);
+                    break;
+                case (int) TAG:
+                    String tag = pis.readString(TAG);
+                    os.write(TAG, tag);
+                    break;
+                default:
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inGroupToken);
+        os.end(outGroupToken);
+    }
+
+    private static void writeViewerConfigMessage(
+            ProtoInputStream pis, ProtoOutputStream os) throws IOException {
+        final long inMessageToken = pis.start(MESSAGES);
+        final long outMessagesToken = os.start(MESSAGES);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) MessageData.MESSAGE_ID:
+                    os.write(MessageData.MESSAGE_ID,
+                            pis.readLong(MessageData.MESSAGE_ID));
+                    break;
+                case (int) MESSAGE:
+                    os.write(MESSAGE, pis.readString(MESSAGE));
+                    break;
+                case (int) LEVEL:
+                    os.write(LEVEL, pis.readInt(LEVEL));
+                    break;
+                case (int) GROUP_ID:
+                    os.write(GROUP_ID, pis.readInt(GROUP_ID));
+                    break;
+                default:
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inMessageToken);
+        os.end(outMessagesToken);
+    }
+
     private void logToLogcat(String tag, LogLevel level, long messageHash,
             @Nullable String messageString, Object[] args) {
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToLogcat");
@@ -423,7 +439,7 @@
         return sw.toString();
     }
 
-    private int internStacktraceString(TracingContext<ProtoLogDataSource.Instance,
+    private int internStacktraceString(TracingContext<
             ProtoLogDataSource.TlsState,
             ProtoLogDataSource.IncrementalState> ctx,
             String stacktrace) {
@@ -433,9 +449,7 @@
     }
 
     private int internStringArg(
-            TracingContext<ProtoLogDataSource.Instance,
-            ProtoLogDataSource.TlsState,
-            ProtoLogDataSource.IncrementalState> ctx,
+            TracingContext<ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx,
             String string
     ) {
         final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
@@ -444,9 +458,7 @@
     }
 
     private int internString(
-            TracingContext<ProtoLogDataSource.Instance,
-                ProtoLogDataSource.TlsState,
-                ProtoLogDataSource.IncrementalState> ctx,
+            TracingContext<ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx,
             Map<String, Integer> internMap,
             long fieldId,
             String string
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 593bdf0..163f32e 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -110,3 +110,6 @@
 
 # PerformanceHintManager
 per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS
+
+# IF Tools
+per-file android_tracing_Perfetto* = file:platform/development:/tools/winscope/OWNERS
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 9525605..30d9ea1 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -225,7 +225,7 @@
     nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
 }
 
-static void android_os_MessageQueue_nativeWake(jlong ptr) {
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
     NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
     nativeMessageQueue->wake();
 }
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 25ff853..5c7b470 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -45,12 +45,6 @@
 static struct {
     jclass clazz;
     jmethodID init;
-    jmethodID getAndClearAllPendingTracePackets;
-} gTracingContextClassInfo;
-
-static struct {
-    jclass clazz;
-    jmethodID init;
 } gCreateTlsStateArgsClassInfo;
 
 static struct {
@@ -68,32 +62,10 @@
     jobject jobj;
 };
 
-static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) {
-    jobjectArray packets =
-            (jobjectArray)env
-                    ->CallObjectMethod(jCtx,
-                                       gTracingContextClassInfo.getAndClearAllPendingTracePackets);
-    if (env->ExceptionOccurred()) {
-        env->ExceptionDescribe();
-        env->ExceptionClear();
-
-        LOG_ALWAYS_FATAL("Failed to call java context finalize method");
-    }
-
-    int packets_count = env->GetArrayLength(packets);
-    for (int i = 0; i < packets_count; i++) {
-        jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
-
-        jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
-        int buffer_size = env->GetArrayLength(packet_proto_buffer);
-
-        struct PerfettoDsRootTracePacket trace_packet;
-        PerfettoDsTracerPacketBegin(ctx, &trace_packet);
-        PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
-                                 buffer_size);
-        PerfettoDsTracerPacketEnd(ctx, &trace_packet);
-    }
-}
+// In a single thread there can be only one trace point active across all data source, so we can use
+// a single global thread_local variable to keep track of the active tracer iterator.
+thread_local static bool gInIteration;
+thread_local static struct PerfettoDsTracerIterator gIterator;
 
 PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource,
                                        std::string dataSourceName)
@@ -164,44 +136,89 @@
     return env->NewGlobalRef(incrementalState.get());
 }
 
-void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
-    PERFETTO_DS_TRACE(dataSource, ctx) {
-        ALOG(LOG_DEBUG, LOG_TAG, "\tin native trace callback function %p", this);
-        TlsState* tls_state =
-                reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
-        IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
-                PerfettoDsGetIncrementalState(&dataSource, &ctx));
+bool PerfettoDataSource::TraceIterateBegin() {
+    if (gInIteration) {
+        return false;
+    }
 
-        ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state = %p", tls_state);
-        ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state = %p", incr_state);
+    gIterator = PerfettoDsTraceIterateBegin(&dataSource);
 
-        ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state->jobj = %p", tls_state->jobj);
-        ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state->jobj = %p", incr_state->jobj);
+    if (gIterator.impl.tracer == nullptr) {
+        return false;
+    }
 
-        ScopedLocalRef<jobject> jCtx(env,
-                                     env->NewObject(gTracingContextClassInfo.clazz,
-                                                    gTracingContextClassInfo.init, &ctx,
-                                                    tls_state->jobj, incr_state->jobj));
+    gInIteration = true;
+    return true;
+}
 
-        ALOG(LOG_DEBUG, LOG_TAG, "\t jCtx = %p", jCtx.get());
+bool PerfettoDataSource::TraceIterateNext() {
+    if (!gInIteration) {
+        LOG_ALWAYS_FATAL("Tried calling TraceIterateNext outside of a tracer iteration.");
+        return false;
+    }
 
-        jclass objclass = env->GetObjectClass(traceFunction);
-        jmethodID method =
-                env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
-        if (method == 0) {
-            LOG_ALWAYS_FATAL("Failed to get method id");
-        }
+    PerfettoDsTraceIterateNext(&dataSource, &gIterator);
 
-        env->ExceptionClear();
+    if (gIterator.impl.tracer == nullptr) {
+        // Reached end of iterator. No more datasource instances.
+        gInIteration = false;
+        return false;
+    }
 
-        env->CallVoidMethod(traceFunction, method, jCtx.get());
-        if (env->ExceptionOccurred()) {
-            env->ExceptionDescribe();
-            env->ExceptionClear();
-            LOG_ALWAYS_FATAL("Failed to call java trace method");
-        }
+    return true;
+}
 
-        traceAllPendingPackets(env, jCtx.get(), &ctx);
+void PerfettoDataSource::TraceIterateBreak() {
+    if (!gInIteration) {
+        return;
+    }
+
+    PerfettoDsTraceIterateBreak(&dataSource, &gIterator);
+    gInIteration = false;
+}
+
+jobject PerfettoDataSource::GetCustomTls() {
+    if (!gInIteration) {
+        LOG_ALWAYS_FATAL("Tried getting CustomTls outside of a tracer iteration.");
+        return nullptr;
+    }
+
+    TlsState* tls_state =
+            reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &gIterator));
+
+    return tls_state->jobj;
+}
+
+jobject PerfettoDataSource::GetIncrementalState() {
+    if (!gInIteration) {
+        LOG_ALWAYS_FATAL("Tried getting IncrementalState outside of a tracer iteration.");
+        return nullptr;
+    }
+
+    IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+            PerfettoDsGetIncrementalState(&dataSource, &gIterator));
+
+    return incr_state->jobj;
+}
+
+void PerfettoDataSource::WritePackets(JNIEnv* env, jobjectArray packets) {
+    if (!gInIteration) {
+        LOG_ALWAYS_FATAL("Tried writing packets outside of a tracer iteration.");
+        return;
+    }
+
+    int packets_count = env->GetArrayLength(packets);
+    for (int i = 0; i < packets_count; i++) {
+        jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
+
+        jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+        int buffer_size = env->GetArrayLength(packet_proto_buffer);
+
+        struct PerfettoDsRootTracePacket trace_packet;
+        PerfettoDsTracerPacketBegin(&gIterator, &trace_packet);
+        PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
+                                 buffer_size);
+        PerfettoDsTracerPacketEnd(&gIterator, &trace_packet);
     }
 }
 
@@ -218,9 +235,7 @@
 
 jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
     const char* nativeString = env->GetStringUTFChars(name, 0);
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p, %s)", javaDataSource, nativeString);
     PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
-    ALOG(LOG_DEBUG, LOG_TAG, "\tdatasource* = %p", dataSource);
     env->ReleaseStringUTFChars(name, nativeString);
 
     dataSource->incStrong((void*)nativeCreate);
@@ -229,39 +244,27 @@
 }
 
 void nativeDestroy(void* ptr) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p)", ptr);
     PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
     dataSource->decStrong((void*)nativeCreate);
 }
 
 static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeGetFinalizer()");
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
 }
 
-void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeTrace(%p)", (void*)dataSourcePtr);
-    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
-
-    datasource->trace(env, traceFunctionInterface);
-}
-
-void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p, %p)", jCtx, (void*)ctxPtr);
-    auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
-    traceAllPendingPackets(env, jCtx, ctx);
-    PerfettoDsTracerFlush(ctx, nullptr, nullptr);
+void nativeFlush(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p)", (void*)ds_ptr);
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr);
+    datasource->WritePackets(env, packets);
 }
 
 void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlushAll(%p)", (void*)ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
     datasource->flushAll();
 }
 
 void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
                               int buffer_exhausted_policy) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeRegisterDataSource(%p)", (void*)datasource_ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
 
     struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -283,10 +286,6 @@
 
         auto* datasource_instance =
                 new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
-
-        ALOG(LOG_DEBUG, LOG_TAG, "on_setup_cb ds=%p, ds_instance=%p", datasource,
-             datasource_instance);
-
         return static_cast<void*>(datasource_instance);
     };
 
@@ -299,9 +298,6 @@
         jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
 
         auto* tls_state = new TlsState(java_tls_state);
-
-        ALOG(LOG_DEBUG, LOG_TAG, "on_create_tls_cb ds=%p, tsl_state=%p", datasource, tls_state);
-
         return static_cast<void*>(tls_state);
     };
 
@@ -310,8 +306,6 @@
 
         TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
 
-        ALOG(LOG_DEBUG, LOG_TAG, "on_delete_tls_cb %p", tls_state);
-
         env->DeleteGlobalRef(tls_state->jobj);
         delete tls_state;
     };
@@ -324,9 +318,6 @@
         jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
 
         auto* incr_state = new IncrementalState(java_incr_state);
-
-        ALOG(LOG_DEBUG, LOG_TAG, "on_create_incr_cb ds=%p, incr_state=%p", datasource, incr_state);
-
         return static_cast<void*>(incr_state);
     };
 
@@ -335,8 +326,6 @@
 
         IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
 
-        ALOG(LOG_DEBUG, LOG_TAG, "on_delete_incr_cb incr_state=%p", incr_state);
-
         env->DeleteGlobalRef(incr_state->jobj);
         delete incr_state;
     };
@@ -346,9 +335,6 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-
-        ALOG(LOG_DEBUG, LOG_TAG, "on_start_cb ds_instance=%p", datasource_instance);
-
         datasource_instance->onStart(env);
     };
 
@@ -357,9 +343,6 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-
-        ALOG(LOG_DEBUG, LOG_TAG, "on_flush_cb ds_instance=%p", datasource_instance);
-
         datasource_instance->onFlush(env);
     };
 
@@ -368,9 +351,6 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-
-        ALOG(LOG_DEBUG, LOG_TAG, "on_stop_cb ds_instance=%p", datasource_instance);
-
         datasource_instance->onStop(env);
     };
 
@@ -378,18 +358,14 @@
                               void* inst_ctx) -> void {
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
 
-        ALOG(LOG_DEBUG, LOG_TAG, "on_destroy_cb ds_instance=%p", datasource_instance);
-
         delete datasource_instance;
     };
 
     PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params);
 }
 
-jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+jobject nativeGetPerfettoInstanceLocked(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr,
                                         PerfettoDsInstanceIndex instance_idx) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeGetPerfettoInstanceLocked ds=%p, idx=%d", (void*)dataSourcePtr,
-         instance_idx);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
     auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
             PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
@@ -401,24 +377,44 @@
         return nullptr;
     }
 
-    ALOG(LOG_DEBUG, LOG_TAG, "\tnativeGetPerfettoInstanceLocked got lock ds=%p, idx=%d",
-         (void*)dataSourcePtr, instance_idx);
     return datasource_instance->GetJavaDataSourceInstance();
 }
 
-void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+void nativeReleasePerfettoInstanceLocked(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr,
                                          PerfettoDsInstanceIndex instance_idx) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeReleasePerfettoInstanceLocked got lock ds=%p, idx=%d",
-         (void*)dataSourcePtr, instance_idx);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
     PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
 }
 
+bool nativePerfettoDsTraceIterateBegin(jlong dataSourcePtr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    return datasource->TraceIterateBegin();
+}
+
+bool nativePerfettoDsTraceIterateNext(jlong dataSourcePtr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    return datasource->TraceIterateNext();
+}
+
+void nativePerfettoDsTraceIterateBreak(jlong dataSourcePtr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    return datasource->TraceIterateBreak();
+}
+
+jobject nativeGetCustomTls(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    return datasource->GetCustomTls();
+}
+
+jobject nativeGetIncrementalState(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    return datasource->GetIncrementalState();
+}
+
 const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         {"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
          (void*)nativeCreate},
-        {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace},
         {"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
         {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
         {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
@@ -426,11 +422,16 @@
          (void*)nativeGetPerfettoInstanceLocked},
         {"nativeReleasePerfettoInstanceLocked", "(JI)V",
          (void*)nativeReleasePerfettoInstanceLocked},
-};
+
+        {"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin},
+        {"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext},
+        {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak}};
 
 const JNINativeMethod gMethodsTracingContext[] = {
         /* name, signature, funcPtr */
-        {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush},
+        {"nativeFlush", "(J[[B)V", (void*)nativeFlush},
+        {"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls},
+        {"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState},
 };
 
 int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
@@ -461,14 +462,6 @@
                              "(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/"
                              "Object;");
 
-    clazz = env->FindClass("android/tracing/perfetto/TracingContext");
-    gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
-    gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>",
-                                                     "(JLjava/lang/Object;Ljava/lang/Object;)V");
-    gTracingContextClassInfo.getAndClearAllPendingTracePackets =
-            env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets",
-                             "()[[B");
-
     clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs");
     gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
     gCreateTlsStateArgsClassInfo.init =
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 906d9f5..209de29 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -46,7 +46,15 @@
 
     jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
     jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
-    void trace(JNIEnv* env, jobject trace_function);
+
+    bool TraceIterateBegin();
+    bool TraceIterateNext();
+    void TraceIterateBreak();
+    void WritePackets(JNIEnv* env, jobjectArray packets);
+
+    jobject GetCustomTls();
+    jobject GetIncrementalState();
+
     void flushAll();
 
 private:
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 4e61ff2..3489cac 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -4,7 +4,6 @@
 cinek@google.com
 dsandler@android.com
 dsandler@google.com
-dupin@google.com
 hackbod@android.com
 hackbod@google.com
 ilyamaty@google.com
@@ -49,7 +48,7 @@
 # Wear
 per-file res/*-watch/* = file:/WEAR_OWNERS
 
-# Peformance
+# Performance
 per-file res/values/config.xml = file:/PERFORMANCE_OWNERS
 per-file res/values/symbols.xml = file:/PERFORMANCE_OWNERS
 
@@ -63,3 +62,11 @@
 
 # TV Input Framework
 per-file res/values/config_tv_external_input_logging.xml = file:/services/core/java/com/android/server/tv/OWNERS
+
+# SysUi Color Team
+per-file res/values/colors.xml = arteiro@google.com
+per-file res/values/attrs.xml = arteiro@google.com
+per-file res/values/styles.xml = arteiro@google.com
+per-file res/values/symbols.xml = arteiro@google.com
+per-file res/values/themes_device_defaults.xml = arteiro@google.com
+per-file res/values/styles_material.xml = arteiro@google.com
\ No newline at end of file
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index c8625b9..9bb72d9 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -28,7 +28,7 @@
          standard SMS rate. The user is warned when the destination phone number matches the
          "pattern" or "premium" regexes, and does not match the "free" or "standard" regexes. -->
 
-    <!-- Harmonised European Short Codes are 6 digit numbers starting with 116 (free helplines).
+    <!-- Harmonised European Short Codes are 7 digit numbers starting with 116 (free helplines).
          Premium patterns include short codes from: http://aonebill.com/coverage&tariffs
          and http://mobilcent.com/info-worldwide.asp and extracted from:
          http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
@@ -39,8 +39,8 @@
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
 
-    <!-- Argentina: 5 digits, known short codes listed -->
-    <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" />
+    <!-- Argentina: 6 digits, known short codes listed -->
+    <shortcode country="ar" pattern="\\d{1,6}" free="11711|28291|44077|78887|191289|39010" />
 
     <!-- Armenia: 3-5 digits, emergency numbers 10[123] -->
     <shortcode country="am" pattern="\\d{3,5}" premium="11[2456]1|3024" free="10[123]|71522|71512|71502" />
@@ -67,7 +67,7 @@
     <shortcode country="bh" pattern="\\d{1,5}" free="81181|85999" />
 
     <!-- Brazil: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000" />
+    <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000|2652" />
 
     <!-- Belarus: 4 digits -->
     <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
@@ -163,7 +163,7 @@
     <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
 
     <!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />
+    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457|99265" />
 
     <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
@@ -172,6 +172,9 @@
     <!-- Israel: 1-5 digits, known premium codes listed -->
     <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
 
+    <!-- Iran: 4-6 digits, known premium codes listed -->
+    <shortcode country="ir" pattern="\\d{4,6}" free="700791|700792" />
+
     <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
          https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
     <shortcode country="it" pattern="\\d{5}" premium="44[0-4]\\d{2}|47[0-4]\\d{2}|48[0-4]\\d{2}|44[5-9]\\d{4}|47[5-9]\\d{4}|48[5-9]\\d{4}|455\\d{2}|499\\d{2}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" />
@@ -219,11 +222,11 @@
     <!-- Mozambique: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="mz" pattern="\\d{1,5}" free="1714" />
 
-    <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
+    <!-- Mexico: 4-7 digits (not confirmed), known premium codes listed -->
+    <shortcode country="mx" pattern="\\d{4,7}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346|3030303" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
-    <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
+    <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668|66966" />
 
     <!-- Namibia: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="na" pattern="\\d{1,5}" free="40005" />
@@ -255,6 +258,9 @@
     <!-- Palestine: 5 digits, known premium codes listed -->
     <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
 
+    <!-- Paraguay: 6 digits, known premium codes listed -->
+    <shortcode country="py" pattern="\\d{6}" free="191289" />
+
     <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
 
@@ -275,7 +281,7 @@
     <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" free="6954|8501" standard="2037|2044"/>
 
     <!-- Rwanda: 4 digits -->
-    <shortcode country="rw" pattern="\\d{4}" free="5060" />
+    <shortcode country="rw" pattern="\\d{4}" free="5060|5061" />
 
     <!-- Saudi Arabia -->
     <shortcode country="sa" pattern="\\d{1,5}" free="8145" />
@@ -309,7 +315,10 @@
     <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
 
     <!-- Tanzania: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234" />
+    <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234|15324" />
+
+    <!-- Tunisia: 5 digits, known premium codes listed -->
+    <shortcode country="tn" pattern="\\d{5}" free="85799" />
 
     <!-- Turkey -->
     <shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" />
@@ -324,8 +333,11 @@
          visual voicemail code for T-Mobile: 122 -->
     <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" />
 
-    <!--Uruguay : 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="uy" pattern="\\d{1,5}" free="55002" />
+    <!--Uruguay : 1-6 digits (standard system default, not country specific) -->
+    <shortcode country="uy" pattern="\\d{1,6}" free="55002|191289" />
+
+    <!-- Venezuela: 1-6 digits (standard system default, not country specific) -->
+    <shortcode country="ve" pattern="\\d{1,6}" free="538352" />
 
     <!-- Vietnam: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055|8079" />
@@ -336,6 +348,9 @@
     <!-- South Africa -->
     <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" />
 
+    <!-- Yemen -->
+    <shortcode country="ye" pattern="\\d{1,4}" free="5081" />
+
     <!-- Zimbabwe -->
     <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
 
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 6cc5485..8072d69 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.os.bugreports.tests;
 
+import static android.content.Context.RECEIVER_EXPORTED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertNotNull;
@@ -358,7 +360,10 @@
         // shell UID rather than our own.
         BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
         InstrumentationRegistry.getContext()
-                .registerReceiver(br, new IntentFilter(INTENT_BUGREPORT_FINISHED));
+                .registerReceiver(
+                        br,
+                        new IntentFilter(INTENT_BUGREPORT_FINISHED),
+                        RECEIVER_EXPORTED);
         UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .executeShellCommand("am bug-report");
 
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index a2a5433..c7d5825 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -175,10 +175,12 @@
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.05f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
                 .isTrue()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.2f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue()
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index 6a6e652..bad0485 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 import android.graphics.Matrix;
 import android.platform.test.annotations.Presubmit;
@@ -232,4 +233,66 @@
         assertEquals(10, (int) event.getX());
         assertEquals(20, (int) event.getY());
     }
+
+    @Test
+    public void testSplit() {
+        final int pointerCount = 2;
+        final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+        final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+                0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+                0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+                0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+                0 /* flags */);
+
+        final int idBits = ~0b1 & event.getPointerIdBits();
+        final MotionEvent splitEvent = event.split(idBits);
+        assertEquals(1, splitEvent.getPointerCount());
+        assertEquals(1, splitEvent.getPointerId(0));
+        assertEquals(40, (int) splitEvent.getX());
+        assertEquals(40, (int) splitEvent.getY());
+    }
+
+    @Test
+    public void testSplitFailsWhenNoIdsSpecified() {
+        final int pointerCount = 2;
+        final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+        final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+                0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+                0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+                0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+                0 /* flags */);
+
+        try {
+            final MotionEvent splitEvent = event.split(0);
+            fail("Splitting event with id bits 0 should throw: " + splitEvent);
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testSplitFailsWhenIdBitsDoNotMatch() {
+        final int pointerCount = 2;
+        final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+        final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+                0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+                0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+                0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+                0 /* flags */);
+
+        try {
+            final int idBits = 0b100;
+            final MotionEvent splitEvent = event.split(idBits);
+            fail("Splitting event with id bits that do not match any pointers should throw: "
+                    + splitEvent);
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 365f348..5fab1a0 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -64,6 +64,9 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Vibrator;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.speech.tts.TextToSpeech;
 import android.speech.tts.Voice;
@@ -71,6 +74,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 import android.widget.Toast;
 
@@ -83,9 +87,11 @@
 
 import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -93,11 +99,14 @@
 import java.lang.reflect.Field;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityShortcutControllerTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
     private static final CharSequence PACKAGE_NAME_STRING = "Service name";
     private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -126,6 +135,7 @@
     private @Mock TextToSpeech mTextToSpeech;
     private @Mock Voice mVoice;
     private @Mock Ringtone mRingtone;
+    private @Captor ArgumentCaptor<List<String>> mListCaptor;
 
     private MockContentResolver mContentResolver;
     private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
@@ -410,6 +420,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
@@ -423,6 +434,29 @@
                 captor.capture());
         captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
 
+        verify(mAccessibilityManagerService).enableShortcutsForTargets(
+                eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+        assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+        getController().performAccessibilityShortcut();
+
+        ArgumentCaptor<DialogInterface.OnClickListener> captor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+                captor.capture());
+        captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
         assertThat(
                 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
         ).isEmpty();
@@ -432,6 +466,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
             throws Exception {
         configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -443,6 +479,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
             throws Exception {
         configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -489,6 +527,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -503,15 +542,39 @@
                 captor.capture());
         captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
 
-        assertThat(
-                Settings.Secure.getString(mContentResolver,
-                        ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+        verify(mAccessibilityManagerService).enableShortcutsForTargets(
+                eq(true), eq(HARDWARE), mListCaptor.capture(), anyInt());
+        assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
         assertThat(Settings.Secure.getInt(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
                 AccessibilityShortcutController.DialogStatus.SHOWN);
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
+            throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureDefaultAccessibilityService();
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+        getController().performAccessibilityShortcut();
+
+        ArgumentCaptor<DialogInterface.OnClickListener> captor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+                captor.capture());
+        captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(Settings.Secure.getString(mContentResolver,
+                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.SHOWN);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -526,9 +589,32 @@
                 captor.capture());
         captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
 
-        assertThat(
-                Settings.Secure.getString(mContentResolver,
-                        ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
+        verify(mAccessibilityManagerService).enableShortcutsForTargets(
+                eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+        assertThat(mListCaptor.getValue()).isEmpty();
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
+            throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureDefaultAccessibilityService();
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+        getController().performAccessibilityShortcut();
+
+        ArgumentCaptor<DialogInterface.OnClickListener> captor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+                captor.capture());
+        captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(Settings.Secure.getString(mContentResolver,
+                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
         assertThat(Settings.Secure.getInt(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
                 AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 2ea044c..a14d8e0 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -19,8 +19,10 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -31,8 +33,12 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,10 +53,13 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
+import java.util.List;
 
 /**
  * Unit Tests for
@@ -59,9 +68,13 @@
 @RunWith(AndroidJUnit4.class)
 public class InvisibleToggleAccessibilityServiceTargetTest {
     @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
     @Mock
     private IAccessibilityManager mAccessibilityManagerService;
+    @Captor
+    private ArgumentCaptor<List<String>> mListCaptor;
 
     private static final String ALWAYS_ON_SERVICE_PACKAGE_LABEL = "always on a11y service";
     private static final String ALWAYS_ON_SERVICE_COMPONENT_NAME =
@@ -104,6 +117,32 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
+        mSut.onCheckedChanged(true);
+
+        verify(mAccessibilityManagerService).enableShortcutsForTargets(
+                eq(true),
+                eq(ShortcutConstants.UserShortcutType.HARDWARE),
+                mListCaptor.capture(),
+                anyInt());
+        assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
+        mSut.onCheckedChanged(false);
+        verify(mAccessibilityManagerService).enableShortcutsForTargets(
+                eq(false),
+                eq(ShortcutConstants.UserShortcutType.HARDWARE),
+                mListCaptor.capture(),
+                anyInt());
+        assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
         enableA11yService(/* enable= */ true);
         addShortcutForA11yService(
@@ -116,6 +155,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
         enableA11yService(/* enable= */ false);
         addShortcutForA11yService(
@@ -128,6 +168,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
         enableA11yService(/* enable= */ true);
         addShortcutForA11yService(
@@ -140,6 +181,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
     public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
         enableA11yService(/* enable= */ true);
         addShortcutForA11yService(
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 826adc39..97147a0 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d410d5f..6cf12de 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -709,18 +709,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
-    "2959074735946674755": {
-      "message": "Trying to update display configuration for system\/invalid process.",
-      "level": "WARN",
-      "group": "WM_DEBUG_CONFIGURATION",
-      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
-    },
-    "5668810920995272206": {
-      "message": "Trying to update display configuration for invalid process, pid=%d",
-      "level": "WARN",
-      "group": "WM_DEBUG_CONFIGURATION",
-      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
-    },
     "-1123414663662718691": {
       "message": "setVr2dDisplayId called for: %d",
       "level": "DEBUG",
@@ -3469,6 +3457,12 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
+    "257349083882992098": {
+      "message": "updateWallpaperTokens requestedVisibility=%b on keyguardLocked=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WALLPAPER",
+      "at": "com\/android\/server\/wm\/WallpaperController.java"
+    },
     "7408402065665963407": {
       "message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
       "level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f9347ee..e8b4104 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -424,12 +424,14 @@
 key 582   VOICE_ASSIST
 # Linux KEY_ASSISTANT
 key 583   ASSIST
+key 585   EMOJI_PICKER
 key 656   MACRO_1
 key 657   MACRO_2
 key 658   MACRO_3
 key 659   MACRO_4
 
 # Keys defined by HID usages
+key usage 0x0c0065 SCREENSHOT                FALLBACK_USAGE_MAPPING
 key usage 0x0c0067 WINDOW                    FALLBACK_USAGE_MAPPING
 key usage 0x0c006F BRIGHTNESS_UP             FALLBACK_USAGE_MAPPING
 key usage 0x0c0070 BRIGHTNESS_DOWN           FALLBACK_USAGE_MAPPING
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 250362b..319f115 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -41,12 +41,15 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.io.IOException;
+import java.io.ByteArrayOutputStream;
 import java.io.OutputStream;
 import java.lang.ref.WeakReference;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.WeakHashMap;
 
 public final class Bitmap implements Parcelable {
     private static final String TAG = "Bitmap";
@@ -120,6 +123,11 @@
     }
 
     /**
+     * @hide
+     */
+    private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
+
+    /**
      * Private constructor that must receive an already allocated native bitmap
      * int (pointer).
      */
@@ -162,6 +170,9 @@
                     Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
         }
         registry.registerNativeAllocation(this, nativeBitmap);
+        synchronized (Bitmap.class) {
+          sAllBitmaps.put(this, null);
+        }
     }
 
     /**
@@ -1510,6 +1521,86 @@
     }
 
     /**
+     * @hide
+     */
+    private static final class DumpData {
+        private int count;
+        private int format;
+        private long[] natives;
+        private byte[][] buffers;
+        private int max;
+
+        public DumpData(@NonNull CompressFormat format, int max) {
+            this.max = max;
+            this.format = format.nativeInt;
+            this.natives = new long[max];
+            this.buffers = new byte[max][];
+            this.count = 0;
+        }
+
+        public void add(long nativePtr, byte[] buffer) {
+            natives[count] = nativePtr;
+            buffers[count] = buffer;
+            count = (count >= max) ? max : count + 1;
+        }
+
+        public int size() {
+            return count;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    private static DumpData dumpData = null;
+
+
+    /**
+     * @hide
+     *
+     * Dump all the bitmaps with their contents compressed into dumpData
+     *
+     * @param format  format of the compressed image, null to clear dump data
+     */
+    public static void dumpAll(@Nullable String format) {
+        if (format == null) {
+            /* release the dump data */
+            dumpData = null;
+            return;
+        }
+        final CompressFormat fmt;
+        if (format.equals("jpg") || format.equals("jpeg")) {
+            fmt = CompressFormat.JPEG;
+        } else if (format.equals("png")) {
+            fmt = CompressFormat.PNG;
+        } else if (format.equals("webp")) {
+            fmt = CompressFormat.WEBP_LOSSLESS;
+        } else {
+            Log.w(TAG, "No bitmaps dumped: unrecognized format " + format);
+            return;
+        }
+
+        final ArrayList<Bitmap> allBitmaps;
+        synchronized (Bitmap.class) {
+          allBitmaps = new ArrayList<>(sAllBitmaps.size());
+          for (Bitmap bitmap : sAllBitmaps.keySet()) {
+            if (bitmap != null && !bitmap.isRecycled()) {
+              allBitmaps.add(bitmap);
+            }
+          }
+        }
+
+        dumpData = new DumpData(fmt, allBitmaps.size());
+        for (Bitmap bitmap : allBitmaps) {
+            ByteArrayOutputStream bas = new ByteArrayOutputStream();
+            if (bitmap.compress(fmt, 90, bas)) {
+                dumpData.add(bitmap.getNativeInstance(), bas.toByteArray());
+            }
+        }
+        Log.i(TAG, dumpData.size() + "/" + allBitmaps.size() + " bitmaps dumped");
+    }
+
+    /**
      * Number of bytes of temp storage we use for communicating between the
      * native compressor and the java OutputStream.
      */
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index efbbfc2..24aea37 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -229,4 +229,24 @@
                     "Keystore error while trying to get apps affected by SID.");
         }
     }
+
+    /**
+    * Deletes all keys in all KeyMint devices.
+    * Called by RecoverySystem before rebooting to recovery in order to delete all KeyMint keys,
+    * including synthetic password protector keys (used by LockSettingsService), as well as keys
+    * protecting DE and metadata encryption keys (used by vold). This ensures that FBE-encrypted
+    * data is unrecoverable even if the data wipe in recovery is interrupted or skipped.
+    */
+    public static void deleteAllKeys() throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+        try {
+            getService().deleteAllKeys();
+        } catch (RemoteException | NullPointerException e) {
+            throw new KeyStoreException(SYSTEM_ERROR,
+                    "Failure to connect to Keystore while trying to delete all keys.");
+        } catch (ServiceSpecificException e) {
+            throw new KeyStoreException(e.errorCode,
+                    "Keystore error while trying to delete all keys.");
+        }
+    }
 }
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2cac2e1..2f2215f 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -17,7 +17,6 @@
 package android.security;
 
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.StrictMode;
 
 /**
  * This class provides some constants and helper methods related to Android's Keystore service.
@@ -38,17 +37,4 @@
     public static KeyStore getInstance() {
         return KEY_STORE;
     }
-
-    /**
-     * Add an authentication record to the keystore authorization table.
-     *
-     * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
-     * @return 0 on success, otherwise an error value corresponding to a
-     * {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
-     */
-    public int addAuthToken(byte[] authToken) {
-        StrictMode.noteDiskWrite();
-
-        return Authorization.addAuthToken(authToken);
-    }
 }
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/KeyStoreAuthorization.java
similarity index 82%
rename from keystore/java/android/security/Authorization.java
rename to keystore/java/android/security/KeyStoreAuthorization.java
index 6404c4b..14d715f 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/KeyStoreAuthorization.java
@@ -33,15 +33,21 @@
  * @hide This is the client side for IKeystoreAuthorization AIDL.
  * It shall only be used by biometric authentication providers and Gatekeeper.
  */
-public class Authorization {
-    private static final String TAG = "KeystoreAuthorization";
+public class KeyStoreAuthorization {
+    private static final String TAG = "KeyStoreAuthorization";
 
     public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
 
+    private static final KeyStoreAuthorization sInstance = new KeyStoreAuthorization();
+
+    public static KeyStoreAuthorization getInstance() {
+        return sInstance;
+    }
+
     /**
      * @return an instance of IKeystoreAuthorization
      */
-    public static IKeystoreAuthorization getService() {
+    private IKeystoreAuthorization getService() {
         return IKeystoreAuthorization.Stub.asInterface(
                     ServiceManager.checkService("android.security.authorization"));
     }
@@ -52,7 +58,7 @@
      * @param authToken created by Android authenticators.
      * @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}.
      */
-    public static int addAuthToken(@NonNull HardwareAuthToken authToken) {
+    public int addAuthToken(@NonNull HardwareAuthToken authToken) {
         StrictMode.noteSlowCall("addAuthToken");
         try {
             getService().addAuthToken(authToken);
@@ -70,7 +76,7 @@
      * @param authToken
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int addAuthToken(@NonNull byte[] authToken) {
+    public int addAuthToken(@NonNull byte[] authToken) {
         return addAuthToken(AuthTokenUtils.toHardwareAuthToken(authToken));
     }
 
@@ -82,7 +88,7 @@
      *                   is LSKF (or equivalent) and thus has made the synthetic password available
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onDeviceUnlocked(int userId, @Nullable byte[] password) {
+    public int onDeviceUnlocked(int userId, @Nullable byte[] password) {
         StrictMode.noteDiskWrite();
         try {
             getService().onDeviceUnlocked(userId, password);
@@ -103,7 +109,7 @@
      * @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+    public int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
             boolean weakUnlockEnabled) {
         StrictMode.noteDiskWrite();
         try {
@@ -125,14 +131,17 @@
      * @return the last authentication time or
      * {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}.
      */
-    public static long getLastAuthenticationTime(
-            long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
+    public long getLastAuthTime(long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
         try {
             return getService().getLastAuthTime(userId, authenticatorTypes);
         } catch (RemoteException | NullPointerException e) {
-            Log.w(TAG, "Can not connect to keystore", e);
+            Log.w(TAG, "Error getting last auth time: " + e);
             return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
         } catch (ServiceSpecificException e) {
+            // This is returned when the feature flag test fails in keystore2
+            if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
+                throw new UnsupportedOperationException();
+            }
             return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 97bf8f7..73b2656 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -32,6 +32,7 @@
 import android.app.IActivityTaskManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
 import android.net.Uri;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -81,7 +83,8 @@
 /**
  * Controls the window animation run when a user initiates a back gesture.
  */
-public class BackAnimationController implements RemoteCallable<BackAnimationController> {
+public class BackAnimationController implements RemoteCallable<BackAnimationController>,
+        ConfigurationChangeListener {
     private static final String TAG = "ShellBackPreview";
     private static final int SETTING_VALUE_OFF = 0;
     private static final int SETTING_VALUE_ON = 1;
@@ -248,6 +251,7 @@
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mShellController.addConfigurationChangeListener(this);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -297,6 +301,11 @@
     private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
 
     @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        mShellBackAnimationRegistry.onConfigurationChanged(newConfig);
+    }
+
+    @Override
     public Context getContext() {
         return mContext;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index c6d4620..112ed09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -68,7 +68,7 @@
 
     private val backAnimRect = Rect()
 
-    private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+    private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
 
     private val backAnimationRunner = BackAnimationRunner(
         Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY
@@ -94,6 +94,10 @@
     private var scrimLayer: SurfaceControl? = null
     private var maxScrimAlpha: Float = 0f
 
+    override fun onConfigurationChanged(newConfiguration: Configuration) {
+        cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+    }
+
     override fun getRunner() = backAnimationRunner
 
     private fun startBackAnimation(backMotionEvent: BackMotionEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 987001d..c34f30d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -29,6 +29,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -80,7 +81,7 @@
     private static final int POST_ANIMATION_DURATION_MS = 500;
 
     private final Rect mStartTaskRect = new Rect();
-    private final float mCornerRadius;
+    private float mCornerRadius;
 
     // The closing window properties.
     private final Rect mClosingStartRect = new Rect();
@@ -120,6 +121,11 @@
         mContext = context;
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+    }
+
     private static float mapRange(float value, float min, float max) {
         return min + (value * (max - min));
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index e33aa75..838dab4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -29,6 +29,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -63,7 +64,7 @@
 public class CustomizeActivityAnimation extends ShellBackAnimation {
     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
     private final BackAnimationRunner mBackAnimationRunner;
-    private final float mCornerRadius;
+    private float mCornerRadius;
     private final SurfaceControl.Transaction mTransaction;
     private final BackAnimationBackground mBackground;
     private RemoteAnimationTarget mEnteringTarget;
@@ -88,6 +89,7 @@
     final Transformation mTransformation = new Transformation();
 
     private final Choreographer mChoreographer;
+    private final Context mContext;
 
     @Inject
     public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
@@ -108,6 +110,12 @@
                 .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
         mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
         mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
+        mContext = context;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
     }
 
     private float getLatestProgress() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
index dc65919..8a0daaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.back;
 
+import android.content.res.Configuration;
 import android.window.BackNavigationInfo;
 
 import javax.inject.Qualifier;
@@ -48,4 +49,8 @@
     public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
         return false;
     }
+
+    void onConfigurationChanged(Configuration newConfig) {
+
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
index 26d2097..00daddc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.res.Configuration;
 import android.util.Log;
 import android.util.SparseArray;
 import android.window.BackNavigationInfo;
@@ -29,6 +30,7 @@
     private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
     private final ShellBackAnimation mDefaultCrossActivityAnimation;
     private final ShellBackAnimation mCustomizeActivityAnimation;
+    private final ShellBackAnimation mCrossTaskAnimation;
 
     public ShellBackAnimationRegistry(
             @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
@@ -57,6 +59,7 @@
 
         mDefaultCrossActivityAnimation = crossActivityAnimation;
         mCustomizeActivityAnimation = customizeActivityAnimation;
+        mCrossTaskAnimation = crossTaskAnimation;
 
         // TODO(b/236760237): register dialog close animation when it's completed.
     }
@@ -125,6 +128,12 @@
                 BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
     }
 
+    void onConfigurationChanged(Configuration newConfig) {
+        mCustomizeActivityAnimation.onConfigurationChanged(newConfig);
+        mDefaultCrossActivityAnimation.onConfigurationChanged(newConfig);
+        mCrossTaskAnimation.onConfigurationChanged(newConfig);
+    }
+
     BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
         int type = backNavigationInfo.getType();
         // Initiate customized cross-activity animation, or fall back to cross activity animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 313d0d2..d295877 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -455,8 +455,7 @@
                         ProtoLog.d(WM_SHELL_BUBBLES,
                                 "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
                                 task.taskId, b.getKey());
-                        mBubbleData.setSelectedBubble(b);
-                        mBubbleData.setExpanded(true);
+                        mBubbleData.setSelectedBubbleAndExpandStack(b);
                         return;
                     }
                 }
@@ -593,13 +592,6 @@
         }
     }
 
-    private void openBubbleOverflow() {
-        ensureBubbleViewsAndWindowCreated();
-        mBubbleData.setShowingOverflow(true);
-        mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
-        mBubbleData.setExpanded(true);
-    }
-
     /**
      * Called when the status bar has become visible or invisible (either permanently or
      * temporarily).
@@ -1247,8 +1239,7 @@
         }
         if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
             // already in the stack
-            mBubbleData.setSelectedBubble(b);
-            mBubbleData.setExpanded(true);
+            mBubbleData.setSelectedBubbleAndExpandStack(b);
         } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
             // promote it out of the overflow
             promoteBubbleFromOverflow(b);
@@ -1273,8 +1264,7 @@
             String key = entry.getKey();
             Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
             if (bubble != null) {
-                mBubbleData.setSelectedBubble(bubble);
-                mBubbleData.setExpanded(true);
+                mBubbleData.setSelectedBubbleAndExpandStack(bubble);
             } else {
                 bubble = mBubbleData.getOverflowBubbleWithKey(key);
                 if (bubble != null) {
@@ -1367,8 +1357,7 @@
             } else {
                 // App bubble is not selected, select it & expand
                 Log.i(TAG, "  showOrHideAppBubble, expand and select existing app bubble");
-                mBubbleData.setSelectedBubble(existingAppBubble);
-                mBubbleData.setExpanded(true);
+                mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble);
             }
         } else {
             // Check if it exists in the overflow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 61f0ed2..ae3d0c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -365,6 +365,19 @@
         mSelectedBubble = bubble;
     }
 
+    /**
+     * Sets the selected bubble and expands it.
+     *
+     * <p>This dispatches a single state update for both changes and should be used instead of
+     * calling {@link #setSelectedBubble(BubbleViewProvider)} followed by
+     * {@link #setExpanded(boolean)} immediately after, which will generate 2 separate updates.
+     */
+    public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble) {
+        setSelectedBubbleInternal(bubble);
+        setExpandedInternal(true);
+        dispatchPendingChanges();
+    }
+
     public void setSelectedBubble(BubbleViewProvider bubble) {
         setSelectedBubbleInternal(bubble);
         dispatchPendingChanges();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index b86e39f..4eff3f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -23,19 +23,25 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip2.phone.PhonePipMenuController;
 import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipMotionHelper;
 import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTouchHandler;
 import com.android.wm.shell.pip2.phone.PipTransition;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellController;
@@ -62,6 +68,7 @@
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             Optional<PipController> pipController,
+            PipTouchHandler pipTouchHandler,
             @NonNull PipScheduler pipScheduler) {
         return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                 pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
@@ -109,4 +116,34 @@
         return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
                 systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
     }
+
+
+    @WMSingleton
+    @Provides
+    static PipTouchHandler providePipTouchHandler(Context context,
+            ShellInit shellInit,
+            PhonePipMenuController menuPhoneController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            @NonNull PipBoundsState pipBoundsState,
+            @NonNull SizeSpecSource sizeSpecSource,
+            PipMotionHelper pipMotionHelper,
+            FloatingContentCoordinator floatingContentCoordinator,
+            PipUiEventLogger pipUiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+                pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator,
+                pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipMotionHelper providePipMotionHelper(Context context,
+            PipBoundsState pipBoundsState, PhonePipMenuController menuController,
+            PipSnapAlgorithm pipSnapAlgorithm,
+            FloatingContentCoordinator floatingContentCoordinator,
+            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+        return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
+                floatingContentCoordinator, pipPerfHintControllerOptional);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
new file mode 100644
index 0000000..e7e7970
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -0,0 +1,311 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+
+import kotlin.Unit;
+
+/**
+ * Handler of all Magnetized Object related code for PiP.
+ */
+public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListener {
+
+    /* The multiplier to apply scale the target size by when applying the magnetic field radius */
+    private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+
+    /**
+     * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+     * PIP.
+     */
+    private MagnetizedObject<Rect> mMagnetizedPip;
+
+    /**
+     * Container for the dismiss circle, so that it can be animated within the container via
+     * translation rather than within the WindowManager via slow layout animations.
+     */
+    private DismissView mTargetViewContainer;
+
+    /** Circle view used to render the dismiss target. */
+    private DismissCircleView mTargetView;
+
+    /**
+     * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+     */
+    private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+    // Allow dragging the PIP to a location to close it
+    private boolean mEnableDismissDragToEdge;
+
+    private int mTargetSize;
+    private int mDismissAreaHeight;
+    private float mMagneticFieldRadiusPercent = 1f;
+    private WindowInsets mWindowInsets;
+
+    private SurfaceControl mTaskLeash;
+    private boolean mHasDismissTargetSurface;
+
+    private final Context mContext;
+    private final PipMotionHelper mMotionHelper;
+    private final PipUiEventLogger mPipUiEventLogger;
+    private final WindowManager mWindowManager;
+    private final ShellExecutor mMainExecutor;
+
+    public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
+            PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
+        mContext = context;
+        mPipUiEventLogger = pipUiEventLogger;
+        mMotionHelper = motionHelper;
+        mMainExecutor = mainExecutor;
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    void init() {
+        Resources res = mContext.getResources();
+        mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
+        mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+        if (mTargetViewContainer != null) {
+            // init can be called multiple times, remove the old one from view hierarchy first.
+            cleanUpDismissTarget();
+        }
+
+        mTargetViewContainer = new DismissView(mContext);
+        DismissViewUtils.setup(mTargetViewContainer);
+        mTargetView = mTargetViewContainer.getCircle();
+        mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+            if (!windowInsets.equals(mWindowInsets)) {
+                mWindowInsets = windowInsets;
+                updateMagneticTargetSize();
+            }
+            return windowInsets;
+        });
+
+        mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+        mMagnetizedPip.clearAllTargets();
+        mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+        updateMagneticTargetSize();
+
+        mMagnetizedPip.setAnimateStuckToTarget(
+                (target, velX, velY, flung, after) -> {
+                    if (mEnableDismissDragToEdge) {
+                        mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+                    }
+                    return Unit.INSTANCE;
+                });
+        mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+            @Override
+            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    @NonNull MagnetizedObject<?> draggedObject) {
+                // Show the dismiss target, in case the initial touch event occurred within
+                // the magnetic field radius.
+                if (mEnableDismissDragToEdge) {
+                    showDismissTargetMaybe();
+                }
+            }
+
+            @Override
+            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    @NonNull MagnetizedObject<?> draggedObject,
+                    float velX, float velY, boolean wasFlungOut) {
+                if (wasFlungOut) {
+                    mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+                    hideDismissTargetMaybe();
+                } else {
+                    mMotionHelper.setSpringingToTouch(true);
+                }
+            }
+
+            @Override
+            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    @NonNull MagnetizedObject<?> draggedObject) {
+                if (mEnableDismissDragToEdge) {
+                    mMainExecutor.executeDelayed(() -> {
+                        mMotionHelper.notifyDismissalPending();
+                        mMotionHelper.animateDismiss();
+                        hideDismissTargetMaybe();
+
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+                    }, 0);
+                }
+            }
+        });
+
+    }
+
+    @Override
+    public boolean onPreDraw() {
+        mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+        mHasDismissTargetSurface = true;
+        updateDismissTargetLayer();
+        return true;
+    }
+
+    /**
+     * Potentially start consuming future motion events if PiP is currently near the magnetized
+     * object.
+     */
+    public boolean maybeConsumeMotionEvent(MotionEvent ev) {
+        return mMagnetizedPip.maybeConsumeMotionEvent(ev);
+    }
+
+    /**
+     * Update the magnet size.
+     */
+    public void updateMagneticTargetSize() {
+        if (mTargetView == null) {
+            return;
+        }
+        if (mTargetViewContainer != null) {
+            mTargetViewContainer.updateResources();
+        }
+
+        final Resources res = mContext.getResources();
+        mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+        mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+        // Set the magnetic field radius equal to the target size from the center of the target
+        setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
+    }
+
+    /**
+     * Increase or decrease the field radius of the magnet object, e.g. with larger percent,
+     * PiP will magnetize to the field sooner.
+     */
+    public void setMagneticFieldRadiusPercent(float percent) {
+        mMagneticFieldRadiusPercent = percent;
+        mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize
+                        * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+    }
+
+    public void setTaskLeash(SurfaceControl taskLeash) {
+        mTaskLeash = taskLeash;
+    }
+
+    private void updateDismissTargetLayer() {
+        if (!mHasDismissTargetSurface || mTaskLeash == null) {
+            // No dismiss target surface, can just return
+            return;
+        }
+
+        final SurfaceControl targetViewLeash =
+                mTargetViewContainer.getViewRootImpl().getSurfaceControl();
+        if (!targetViewLeash.isValid()) {
+            // The surface of mTargetViewContainer is somehow not ready, bail early
+            return;
+        }
+
+        // Put the dismiss target behind the task
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.setRelativeLayer(targetViewLeash, mTaskLeash, -1);
+        t.apply();
+    }
+
+    /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+    public void createOrUpdateDismissTarget() {
+        if (mTargetViewContainer.getParent() == null) {
+            mTargetViewContainer.cancelAnimators();
+
+            mTargetViewContainer.setVisibility(View.INVISIBLE);
+            mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+            mHasDismissTargetSurface = false;
+
+            mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
+        } else {
+            mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
+        }
+    }
+
+    /** Returns layout params for the dismiss target, using the latest display metrics. */
+    private WindowManager.LayoutParams getDismissTargetLayoutParams() {
+        final Point windowSize = new Point();
+        mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+        int height = Math.min(windowSize.y, mDismissAreaHeight);
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.MATCH_PARENT,
+                height,
+                0, windowSize.y - height,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSLUCENT);
+
+        lp.setTitle("pip-dismiss-overlay");
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        lp.setFitInsetsTypes(0 /* types */);
+
+        return lp;
+    }
+
+    /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+    public void showDismissTargetMaybe() {
+        if (!mEnableDismissDragToEdge) {
+            return;
+        }
+
+        createOrUpdateDismissTarget();
+
+        if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+            mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
+        }
+        // always invoke show, since the target might still be VISIBLE while playing hide animation,
+        // so we want to ensure it will show back again
+        mTargetViewContainer.show();
+    }
+
+    /** Animates the magnetic dismiss target out and then sets it to GONE. */
+    public void hideDismissTargetMaybe() {
+        if (!mEnableDismissDragToEdge) {
+            return;
+        }
+        mTargetViewContainer.hide();
+    }
+
+    /**
+     * Removes the dismiss target and cancels any pending callbacks to show it.
+     */
+    public void cleanUpDismissTarget() {
+        if (mTargetViewContainer.getParent() != null) {
+            mWindowManager.removeViewImmediate(mTargetViewContainer);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
new file mode 100644
index 0000000..619bed4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -0,0 +1,719 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Debug;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.FloatProperties;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * A helper to animate and manipulate the PiP.
+ */
+public class PipMotionHelper implements PipAppOpsListener.Callback,
+        FloatingContentCoordinator.FloatingContent {
+    private static final String TAG = "PipMotionHelper";
+    private static final boolean DEBUG = false;
+
+    private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
+    private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+    private static final int UNSTASH_DURATION = 250;
+    private static final int LEAVE_PIP_DURATION = 300;
+    private static final int SHIFT_DURATION = 300;
+
+    /** Friction to use for PIP when it moves via physics fling animations. */
+    private static final float DEFAULT_FRICTION = 1.9f;
+    /** How much of the dismiss circle size to use when scaling down PIP. **/
+    private static final float DISMISS_CIRCLE_PERCENT = 0.85f;
+
+    private final Context mContext;
+    private @NonNull PipBoundsState mPipBoundsState;
+
+    private PhonePipMenuController mMenuController;
+    private PipSnapAlgorithm mSnapAlgorithm;
+
+    /** The region that all of PIP must stay within. */
+    private final Rect mFloatingAllowedArea = new Rect();
+
+    /** Coordinator instance for resolving conflicts with other floating content. */
+    private FloatingContentCoordinator mFloatingContentCoordinator;
+
+    @Nullable private final PipPerfHintController mPipPerfHintController;
+    @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+    /**
+     * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
+     * using physics animations.
+     */
+    private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
+
+    private MagnetizedObject<Rect> mMagnetizedPip;
+
+    /**
+     * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}.
+     */
+    private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
+
+    /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
+    private PhysicsAnimator.FlingConfig mFlingConfigX;
+    private PhysicsAnimator.FlingConfig mFlingConfigY;
+    /** FlingConfig instances provided to PhysicsAnimator for stashing. */
+    private PhysicsAnimator.FlingConfig mStashConfigX;
+
+    /** SpringConfig to use for fling-then-spring animations. */
+    private final PhysicsAnimator.SpringConfig mSpringConfig =
+            new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+
+    /** SpringConfig used for animating into the dismiss region, matches the one in
+     * {@link MagnetizedObject}. */
+    private final PhysicsAnimator.SpringConfig mAnimateToDismissSpringConfig =
+            new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_NO_BOUNCY);
+
+    /** SpringConfig used for animating the pip to catch up to the finger once it leaves the dismiss
+     * drag region. */
+    private final PhysicsAnimator.SpringConfig mCatchUpSpringConfig =
+            new PhysicsAnimator.SpringConfig(5000f, DAMPING_RATIO_NO_BOUNCY);
+
+    /** SpringConfig to use for springing PIP away from conflicting floating content. */
+    private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
+            new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
+
+    private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
+        if (mPipBoundsState.getBounds().equals(newBounds)) {
+            return;
+        }
+
+        mMenuController.updateMenuLayout(newBounds);
+        mPipBoundsState.setBounds(newBounds);
+    };
+
+    /**
+     * Whether we're springing to the touch event location (vs. moving it to that position
+     * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+     * 'stuck' in the target and needs to catch up to the touch location.
+     */
+    private boolean mSpringingToTouch = false;
+
+    /**
+     * Whether PIP was released in the dismiss target, and will be animated out and dismissed
+     * shortly.
+     */
+    private boolean mDismissalPending = false;
+
+    /**
+     * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
+     * used to show menu activity when the expand animation is completed.
+     */
+    private Runnable mPostPipTransitionCallback;
+
+    public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+            PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
+            FloatingContentCoordinator floatingContentCoordinator,
+            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+        mContext = context;
+        mPipBoundsState = pipBoundsState;
+        mMenuController = menuController;
+        mSnapAlgorithm = snapAlgorithm;
+        mFloatingContentCoordinator = floatingContentCoordinator;
+        mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+        mResizePipUpdateListener = (target, values) -> {
+            if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+                /*
+                mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
+                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
+                 */
+            }
+        };
+    }
+
+    void init() {
+        mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+                mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+    }
+
+    @NonNull
+    @Override
+    public Rect getFloatingBoundsOnScreen() {
+        return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty()
+                ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds();
+    }
+
+    @NonNull
+    @Override
+    public Rect getAllowedFloatingBoundsRegion() {
+        return mFloatingAllowedArea;
+    }
+
+    @Override
+    public void moveToBounds(@NonNull Rect bounds) {
+        animateToBounds(bounds, mConflictResolutionSpringConfig);
+    }
+
+    /**
+     * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
+     */
+    void synchronizePinnedStackBounds() {
+        cancelPhysicsAnimation();
+        mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+
+        /*
+        if (mPipTaskOrganizer.isInPip()) {
+            mFloatingContentCoordinator.onContentMoved(this);
+        }
+         */
+    }
+
+    /**
+     * Tries to move the pinned stack to the given {@param bounds}.
+     */
+    void movePip(Rect toBounds) {
+        movePip(toBounds, false /* isDragging */);
+    }
+
+    /**
+     * Tries to move the pinned stack to the given {@param bounds}.
+     *
+     * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we
+     *                   won't notify the floating content coordinator of this move, since that will
+     *                   happen when the gesture ends.
+     */
+    void movePip(Rect toBounds, boolean isDragging) {
+        if (!isDragging) {
+            mFloatingContentCoordinator.onContentMoved(this);
+        }
+
+        if (!mSpringingToTouch) {
+            // If we are moving PIP directly to the touch event locations, cancel any animations and
+            // move PIP to the given bounds.
+            cancelPhysicsAnimation();
+
+            if (!isDragging) {
+                resizePipUnchecked(toBounds);
+                mPipBoundsState.setBounds(toBounds);
+            } else {
+                mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
+                /*
+                mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
+                        (Rect newBounds) -> {
+                                mMenuController.updateMenuLayout(newBounds);
+                        });
+                 */
+            }
+        } else {
+            // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+            // to spring towards the new touch location.
+            mTemporaryBoundsPhysicsAnimator
+                    .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mCatchUpSpringConfig)
+                    .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mCatchUpSpringConfig)
+                    .spring(FloatProperties.RECT_X, toBounds.left, mCatchUpSpringConfig)
+                    .spring(FloatProperties.RECT_Y, toBounds.top, mCatchUpSpringConfig);
+
+            startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+        }
+    }
+
+    /** Animates the PIP into the dismiss target, scaling it down. */
+    void animateIntoDismissTarget(
+            MagnetizedObject.MagneticTarget target,
+            float velX, float velY,
+            boolean flung, Function0<Unit> after) {
+        final PointF targetCenter = target.getCenterOnScreen();
+
+        // PIP should fit in the circle
+        final float dismissCircleSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.dismiss_circle_size);
+
+        final float width = getBounds().width();
+        final float height = getBounds().height();
+        final float ratio = width / height;
+
+        // Width should be a little smaller than the circle size.
+        final float desiredWidth = dismissCircleSize * DISMISS_CIRCLE_PERCENT;
+        final float desiredHeight = desiredWidth / ratio;
+        final float destinationX = targetCenter.x - (desiredWidth / 2f);
+        final float destinationY = targetCenter.y - (desiredHeight / 2f);
+
+        // If we're already in the dismiss target area, then there won't be a move to set the
+        // temporary bounds, so just initialize it to the current bounds.
+        if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
+            mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+        }
+        mTemporaryBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_X, destinationX, velX, mAnimateToDismissSpringConfig)
+                .spring(FloatProperties.RECT_Y, destinationY, velY, mAnimateToDismissSpringConfig)
+                .spring(FloatProperties.RECT_WIDTH, desiredWidth, mAnimateToDismissSpringConfig)
+                .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mAnimateToDismissSpringConfig)
+                .withEndActions(after);
+
+        startBoundsAnimator(destinationX, destinationY);
+    }
+
+    /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+    void setSpringingToTouch(boolean springingToTouch) {
+        mSpringingToTouch = springingToTouch;
+    }
+
+    /**
+     * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+     *      * fullscreen depending on the display area's windowing mode.
+     */
+    void expandLeavePip(boolean skipAnimation) {
+        expandLeavePip(skipAnimation, false /* enterSplit */);
+    }
+
+    /**
+     * Resizes the pinned task to split-screen mode.
+     */
+    void expandIntoSplit() {
+        expandLeavePip(false, true /* enterSplit */);
+    }
+
+    /**
+     * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+     * fullscreen depending on the display area's windowing mode.
+     */
+    private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: exitPip: skipAnimation=%s"
+                            + " callers=\n%s", TAG, skipAnimation, Debug.getCallers(5, "    "));
+        }
+        cancelPhysicsAnimation();
+        mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+        // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
+    }
+
+    /**
+     * Dismisses the pinned stack.
+     */
+    @Override
+    public void dismissPip() {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, "    "));
+        }
+        cancelPhysicsAnimation();
+        mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
+        // mPipTaskOrganizer.removePip();
+    }
+
+    /** Sets the movement bounds to use to constrain PIP position animations. */
+    void onMovementBoundsChanged() {
+        rebuildFlingConfigs();
+
+        // The movement bounds represent the area within which we can move PIP's top-left position.
+        // The allowed area for all of PIP is those bounds plus PIP's width and height.
+        mFloatingAllowedArea.set(mPipBoundsState.getMovementBounds());
+        mFloatingAllowedArea.right += getBounds().width();
+        mFloatingAllowedArea.bottom += getBounds().height();
+    }
+
+    /**
+     * @return the PiP bounds.
+     */
+    private Rect getBounds() {
+        return mPipBoundsState.getBounds();
+    }
+
+    /**
+     * Flings the PiP to the closest snap target.
+     */
+    void flingToSnapTarget(
+            float velocityX, float velocityY, @Nullable Runnable postBoundsUpdateCallback) {
+        movetoTarget(velocityX, velocityY, postBoundsUpdateCallback, false /* isStash */);
+    }
+
+    /**
+     * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion.
+     */
+    void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) {
+        velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY;
+        movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
+    }
+
+    private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+    private void cleanUpHighPerfSessionMaybe() {
+        if (mPipHighPerfSession != null) {
+            // Close the high perf session once pointer interactions are over;
+            mPipHighPerfSession.close();
+            mPipHighPerfSession = null;
+        }
+    }
+
+    private void movetoTarget(
+            float velocityX,
+            float velocityY,
+            @Nullable Runnable postBoundsUpdateCallback,
+            boolean isStash) {
+        // If we're flinging to a snap target now, we're not springing to catch up to the touch
+        // location now.
+        mSpringingToTouch = false;
+
+        mTemporaryBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
+                .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
+                .flingThenSpring(
+                        FloatProperties.RECT_X, velocityX,
+                        isStash ? mStashConfigX : mFlingConfigX,
+                        mSpringConfig, true /* flingMustReachMinOrMax */)
+                .flingThenSpring(
+                        FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);
+
+        final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+        final float leftEdge = isStash
+                ? mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+                + insetBounds.left
+                : mPipBoundsState.getMovementBounds().left;
+        final float rightEdge = isStash
+                ?  mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+                - insetBounds.right
+                : mPipBoundsState.getMovementBounds().right;
+
+        final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
+
+        final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top;
+        final float estimatedFlingYEndValue =
+                PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
+
+        startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
+                postBoundsUpdateCallback);
+    }
+
+    /**
+     * Animates PIP to the provided bounds, using physics animations and the given spring
+     * configuration
+     */
+    void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
+        if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+            // Animate from the current bounds if we're not already animating.
+            mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+        }
+
+        mTemporaryBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_X, bounds.left, springConfig)
+                .spring(FloatProperties.RECT_Y, bounds.top, springConfig);
+        startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */);
+    }
+
+    /**
+     * Animates the dismissal of the PiP off the edge of the screen.
+     */
+    void animateDismiss() {
+        // Animate off the bottom of the screen, then dismiss PIP.
+        mTemporaryBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_Y,
+                        mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
+                        0,
+                        mSpringConfig)
+                .withEndActions(this::dismissPip);
+
+        startBoundsAnimator(
+                getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
+
+        mDismissalPending = false;
+    }
+
+    /**
+     * Animates the PiP to the expanded state to show the menu.
+     */
+    float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
+            Rect expandedMovementBounds, Runnable callback) {
+        float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+                movementBounds);
+        mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
+        mPostPipTransitionCallback = callback;
+        resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION);
+        return savedSnapFraction;
+    }
+
+    /**
+     * Animates the PiP from the expanded state to the normal state after the menu is hidden.
+     */
+    void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
+            Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) {
+        if (savedSnapFraction < 0f) {
+            // If there are no saved snap fractions, then just use the current bounds
+            savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+                    currentMovementBounds, mPipBoundsState.getStashedState());
+        }
+
+        mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction,
+                mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
+                mPipBoundsState.getDisplayBounds(),
+                mPipBoundsState.getDisplayLayout().stableInsets());
+
+        if (immediate) {
+            movePip(normalBounds);
+        } else {
+            resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION);
+        }
+    }
+
+    /**
+     * Animates the PiP to the stashed state, choosing the closest edge.
+     */
+    void animateToStashedClosestEdge() {
+        Rect tmpBounds = new Rect();
+        final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+        final int stashType =
+                mPipBoundsState.getBounds().left == mPipBoundsState.getMovementBounds().left
+                ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT;
+        final float leftEdge = stashType == STASH_TYPE_LEFT
+                ? mPipBoundsState.getStashOffset()
+                - mPipBoundsState.getBounds().width() + insetBounds.left
+                : mPipBoundsState.getDisplayBounds().right
+                        - mPipBoundsState.getStashOffset() - insetBounds.right;
+        tmpBounds.set((int) leftEdge,
+                mPipBoundsState.getBounds().top,
+                (int) (leftEdge + mPipBoundsState.getBounds().width()),
+                mPipBoundsState.getBounds().bottom);
+        resizeAndAnimatePipUnchecked(tmpBounds, UNSTASH_DURATION);
+        mPipBoundsState.setStashed(stashType);
+    }
+
+    /**
+     * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+     */
+    void animateToUnStashedBounds(Rect unstashedBounds) {
+        resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+    }
+
+    /**
+     * Animates the PiP to offset it from the IME or shelf.
+     */
+    void animateToOffset(Rect originalBounds, int offset) {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: animateToOffset: originalBounds=%s offset=%s"
+                            + " callers=\n%s", TAG, originalBounds, offset,
+                    Debug.getCallers(5, "    "));
+        }
+        cancelPhysicsAnimation();
+        /*
+        mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
+                mUpdateBoundsCallback);
+         */
+    }
+
+    /**
+     * Cancels all existing animations.
+     */
+    private void cancelPhysicsAnimation() {
+        mTemporaryBoundsPhysicsAnimator.cancel();
+        mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+        mSpringingToTouch = false;
+    }
+
+    /** Set new fling configs whose min/max values respect the given movement bounds. */
+    private void rebuildFlingConfigs() {
+        mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+                mPipBoundsState.getMovementBounds().left,
+                mPipBoundsState.getMovementBounds().right);
+        mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+                mPipBoundsState.getMovementBounds().top,
+                mPipBoundsState.getMovementBounds().bottom);
+        final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+        mStashConfigX = new PhysicsAnimator.FlingConfig(
+                DEFAULT_FRICTION,
+                mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+                        + insetBounds.left,
+                mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+                        - insetBounds.right);
+    }
+
+    private void startBoundsAnimator(float toX, float toY) {
+        startBoundsAnimator(toX, toY, null /* postBoundsUpdateCallback */);
+    }
+
+    /**
+     * Starts the physics animator which will update the animated PIP bounds using physics
+     * animations, as well as the TimeAnimator which will apply those bounds to PIP.
+     *
+     * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
+     * the 'real' bounds to equal the final animated bounds.
+     *
+     * If one wishes to supply a callback after all the 'real' bounds update has happened,
+     * pass @param postBoundsUpdateCallback.
+     */
+    private void startBoundsAnimator(float toX, float toY, Runnable postBoundsUpdateCallback) {
+        if (!mSpringingToTouch) {
+            cancelPhysicsAnimation();
+        }
+
+        setAnimatingToBounds(new Rect(
+                (int) toX,
+                (int) toY,
+                (int) toX + getBounds().width(),
+                (int) toY + getBounds().height()));
+
+        if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+            if (mPipPerfHintController != null) {
+                // Start a high perf session with a timeout callback.
+                mPipHighPerfSession = mPipPerfHintController.startSession(
+                        this::onHighPerfSessionTimeout, "startBoundsAnimator");
+            }
+            if (postBoundsUpdateCallback != null) {
+                mTemporaryBoundsPhysicsAnimator
+                        .addUpdateListener(mResizePipUpdateListener)
+                        .withEndActions(this::onBoundsPhysicsAnimationEnd,
+                                postBoundsUpdateCallback);
+            } else {
+                mTemporaryBoundsPhysicsAnimator
+                        .addUpdateListener(mResizePipUpdateListener)
+                        .withEndActions(this::onBoundsPhysicsAnimationEnd);
+            }
+        }
+
+        mTemporaryBoundsPhysicsAnimator.start();
+    }
+
+    /**
+     * Notify that PIP was released in the dismiss target and will be animated out and dismissed
+     * shortly.
+     */
+    void notifyDismissalPending() {
+        mDismissalPending = true;
+    }
+
+    private void onBoundsPhysicsAnimationEnd() {
+        // The physics animation ended, though we may not necessarily be done animating, such as
+        // when we're still dragging after moving out of the magnetic target.
+        if (!mDismissalPending
+                && !mSpringingToTouch
+                && !mMagnetizedPip.getObjectStuckToTarget()) {
+            // All motion operations have actually finished.
+            mPipBoundsState.setBounds(
+                    mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+            mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+            if (!mDismissalPending) {
+                // do not schedule resize if PiP is dismissing, which may cause app re-open to
+                // mBounds instead of its normal bounds.
+                // mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
+            }
+        }
+        mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+        mSpringingToTouch = false;
+        mDismissalPending = false;
+        cleanUpHighPerfSessionMaybe();
+    }
+
+    /**
+     * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
+     * we return these bounds from
+     * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
+     */
+    private void setAnimatingToBounds(Rect bounds) {
+        mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds);
+        mFloatingContentCoordinator.onContentMoved(this);
+    }
+
+    /**
+     * Directly resizes the PiP to the given {@param bounds}.
+     */
+    private void resizePipUnchecked(Rect toBounds) {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: resizePipUnchecked: toBounds=%s"
+                            + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, "    "));
+        }
+        if (!toBounds.equals(getBounds())) {
+            // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
+        }
+    }
+
+    /**
+     * Directly resizes the PiP to the given {@param bounds}.
+     */
+    private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: resizeAndAnimatePipUnchecked: toBounds=%s"
+                            + " duration=%s callers=\n%s", TAG, toBounds, duration,
+                    Debug.getCallers(5, "    "));
+        }
+
+        // Intentionally resize here even if the current bounds match the destination bounds.
+        // This is so all the proper callbacks are performed.
+
+        // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
+        //         TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
+        // setAnimatingToBounds(toBounds);
+    }
+
+    /**
+     * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+     * magnetic dismiss target so it can calculate PIP's size and position.
+     */
+    MagnetizedObject<Rect> getMagnetizedPip() {
+        if (mMagnetizedPip == null) {
+            mMagnetizedPip = new MagnetizedObject<Rect>(
+                    mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(),
+                    FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+                @Override
+                public float getWidth(@NonNull Rect animatedPipBounds) {
+                    return animatedPipBounds.width();
+                }
+
+                @Override
+                public float getHeight(@NonNull Rect animatedPipBounds) {
+                    return animatedPipBounds.height();
+                }
+
+                @Override
+                public void getLocationOnScreen(
+                        @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+                    loc[0] = animatedPipBounds.left;
+                    loc[1] = animatedPipBounds.top;
+                }
+            };
+            mMagnetizedPip.setFlingToTargetEnabled(false);
+        }
+
+        return mMagnetizedPip;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
new file mode 100644
index 0000000..cc8e3e0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -0,0 +1,1081 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.provider.DeviceConfig;
+import android.util.Size;
+import android.view.DisplayCutout;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler {
+
+    private static final String TAG = "PipTouchHandler";
+    private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
+
+    // Allow PIP to resize to a slightly bigger state upon touch
+    private boolean mEnableResize;
+    private final Context mContext;
+    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+    @NonNull private final PipBoundsState mPipBoundsState;
+    @NonNull private final SizeSpecSource mSizeSpecSource;
+    private final PipUiEventLogger mPipUiEventLogger;
+    private final PipDismissTargetHandler mPipDismissTargetHandler;
+    private final ShellExecutor mMainExecutor;
+    @Nullable private final PipPerfHintController mPipPerfHintController;
+
+    private PipResizeGestureHandler mPipResizeGestureHandler;
+
+    private final PhonePipMenuController mMenuController;
+    private final AccessibilityManager mAccessibilityManager;
+
+    /**
+     * Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the
+     * screen, it will be shown in "stashed" mode, where PIP will only show partially.
+     */
+    private boolean mEnableStash = true;
+
+    private float mStashVelocityThreshold;
+
+    // The reference inset bounds, used to determine the dismiss fraction
+    private final Rect mInsetBounds = new Rect();
+
+    // Used to workaround an issue where the WM rotation happens before we are notified, allowing
+    // us to send stale bounds
+    private int mDeferResizeToNormalBoundsUntilRotation = -1;
+    private int mDisplayRotation;
+
+    // Behaviour states
+    private int mMenuState = MENU_STATE_NONE;
+    private boolean mIsImeShowing;
+    private int mImeHeight;
+    private int mImeOffset;
+    private boolean mIsShelfShowing;
+    private int mShelfHeight;
+    private int mMovementBoundsExtraOffsets;
+    private int mBottomOffsetBufferPx;
+    private float mSavedSnapFraction = -1f;
+    private boolean mSendingHoverAccessibilityEvents;
+    private boolean mMovementWithinDismiss;
+
+    // Touch state
+    private final PipTouchState mTouchState;
+    private final FloatingContentCoordinator mFloatingContentCoordinator;
+    private PipMotionHelper mMotionHelper;
+    private PipTouchGesture mGesture;
+
+    // Temp vars
+    private final Rect mTmpBounds = new Rect();
+
+    /**
+     * A listener for the PIP menu activity.
+     */
+    private class PipMenuListener implements PhonePipMenuController.Listener {
+        @Override
+        public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+            PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback);
+        }
+
+        @Override
+        public void onPipMenuStateChangeFinish(int menuState) {
+            setMenuState(menuState);
+        }
+
+        @Override
+        public void onPipExpand() {
+            mMotionHelper.expandLeavePip(false /* skipAnimation */);
+        }
+
+        @Override
+        public void onPipDismiss() {
+            mTouchState.removeDoubleTapTimeoutCallback();
+            mMotionHelper.dismissPip();
+        }
+
+        @Override
+        public void onPipShowMenu() {
+            mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                    true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle());
+        }
+    }
+
+    @SuppressLint("InflateParams")
+    public PipTouchHandler(Context context,
+            ShellInit shellInit,
+            PhonePipMenuController menuController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            @NonNull PipBoundsState pipBoundsState,
+            @NonNull SizeSpecSource sizeSpecSource,
+            PipMotionHelper pipMotionHelper,
+            FloatingContentCoordinator floatingContentCoordinator,
+            PipUiEventLogger pipUiEventLogger,
+            ShellExecutor mainExecutor,
+            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+        mContext = context;
+        mMainExecutor = mainExecutor;
+        mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mPipBoundsAlgorithm = pipBoundsAlgorithm;
+        mPipBoundsState = pipBoundsState;
+        mSizeSpecSource = sizeSpecSource;
+        mMenuController = menuController;
+        mPipUiEventLogger = pipUiEventLogger;
+        mFloatingContentCoordinator = floatingContentCoordinator;
+        mMenuController.addListener(new PipMenuListener());
+        mGesture = new DefaultPipTouchGesture();
+        mMotionHelper = pipMotionHelper;
+        mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
+                mMotionHelper, mainExecutor);
+        mTouchState = new PipTouchState(ViewConfiguration.get(context),
+                () -> {
+                    if (mPipBoundsState.isStashed()) {
+                        animateToUnStashedState();
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    } else {
+                        mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+                                mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+                                willResizeMenu(),
+                                shouldShowResizeHandle());
+                    }
+                },
+                menuController::hideMenu,
+                mainExecutor);
+        mPipResizeGestureHandler =
+                new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+                        mTouchState, this::updateMovementBounds, pipUiEventLogger,
+                        menuController, mainExecutor, mPipPerfHintController);
+
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    /**
+     * Called when the touch handler is initialized.
+     */
+    public void onInit() {
+        Resources res = mContext.getResources();
+        mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
+        reloadResources();
+
+        mMotionHelper.init();
+        mPipResizeGestureHandler.init();
+        mPipDismissTargetHandler.init();
+
+        mEnableStash = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                PIP_STASHING,
+                /* defaultValue = */ true);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mMainExecutor,
+                properties -> {
+                    if (properties.getKeyset().contains(PIP_STASHING)) {
+                        mEnableStash = properties.getBoolean(
+                                PIP_STASHING, /* defaultValue = */ true);
+                    }
+                });
+        mStashVelocityThreshold = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+                DEFAULT_STASH_VELOCITY_THRESHOLD);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mMainExecutor,
+                properties -> {
+                    if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+                        mStashVelocityThreshold = properties.getFloat(
+                                PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+                                DEFAULT_STASH_VELOCITY_THRESHOLD);
+                    }
+                });
+    }
+
+    public PipTransitionController getTransitionHandler() {
+        // return mPipTaskOrganizer.getTransitionController();
+        return null;
+    }
+
+    private void reloadResources() {
+        final Resources res = mContext.getResources();
+        mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
+        mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+        mPipDismissTargetHandler.updateMagneticTargetSize();
+    }
+
+    void onOverlayChanged() {
+        // onOverlayChanged is triggered upon theme change, update the dismiss target accordingly.
+        mPipDismissTargetHandler.init();
+    }
+
+    private boolean shouldShowResizeHandle() {
+        return false;
+    }
+
+    void setTouchGesture(PipTouchGesture gesture) {
+        mGesture = gesture;
+    }
+
+    void setTouchEnabled(boolean enabled) {
+        mTouchState.setAllowTouches(enabled);
+    }
+
+    void showPictureInPictureMenu() {
+        // Only show the menu if the user isn't currently interacting with the PiP
+        if (!mTouchState.isUserInteracting()) {
+            mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                    false /* allowMenuTimeout */, willResizeMenu(),
+                    shouldShowResizeHandle());
+        }
+    }
+
+    void onActivityPinned() {
+        mPipDismissTargetHandler.createOrUpdateDismissTarget();
+
+        mPipResizeGestureHandler.onActivityPinned();
+        mFloatingContentCoordinator.onContentAdded(mMotionHelper);
+    }
+
+    void onActivityUnpinned(ComponentName topPipActivity) {
+        if (topPipActivity == null) {
+            // Clean up state after the last PiP activity is removed
+            mPipDismissTargetHandler.cleanUpDismissTarget();
+
+            mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
+        }
+        mPipResizeGestureHandler.onActivityUnpinned();
+    }
+
+    void onPinnedStackAnimationEnded(
+            @PipAnimationController.TransitionDirection int direction) {
+        // Always synchronize the motion helper bounds once PiP animations finish
+        mMotionHelper.synchronizePinnedStackBounds();
+        updateMovementBounds();
+        if (direction == TRANSITION_DIRECTION_TO_PIP) {
+            // Set the initial bounds as the user resize bounds.
+            mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+        }
+    }
+
+    void onConfigurationChanged() {
+        mPipResizeGestureHandler.onConfigurationChanged();
+        mMotionHelper.synchronizePinnedStackBounds();
+        reloadResources();
+
+        /*
+        if (mPipTaskOrganizer.isInPip()) {
+            // Recreate the dismiss target for the new orientation.
+            mPipDismissTargetHandler.createOrUpdateDismissTarget();
+        }
+         */
+    }
+
+    void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        mIsImeShowing = imeVisible;
+        mImeHeight = imeHeight;
+    }
+
+    void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        mIsShelfShowing = shelfVisible;
+        mShelfHeight = shelfHeight;
+    }
+
+    /**
+     * Called when SysUI state changed.
+     *
+     * @param isSysUiStateValid Is SysUI valid or not.
+     */
+    public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+        mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid);
+    }
+
+    void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
+        final Rect toMovementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+        final int prevBottom = mPipBoundsState.getMovementBounds().bottom
+                - mMovementBoundsExtraOffsets;
+        if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
+            outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
+        }
+    }
+
+    /**
+     * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+     */
+    public void onAspectRatioChanged() {
+        mPipResizeGestureHandler.invalidateUserResizeBounds();
+    }
+
+    void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
+            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+        // Set the user resized bounds equal to the new normal bounds in case they were
+        // invalidated (e.g. by an aspect ratio change).
+        if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+            mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+        }
+
+        final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
+        final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
+        if (fromDisplayRotationChanged) {
+            mTouchState.reset();
+        }
+
+        // Re-calculate the expanded bounds
+        Rect normalMovementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds,
+                normalMovementBounds, bottomOffset);
+
+        if (mPipBoundsState.getMovementBounds().isEmpty()) {
+            // mMovementBounds is not initialized yet and a clean movement bounds without
+            // bottom offset shall be used later in this function.
+            mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
+                    mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */);
+        }
+
+        // Calculate the expanded size
+        float aspectRatio = (float) normalBounds.width() / normalBounds.height();
+        Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
+        mPipBoundsState.setExpandedBounds(
+                new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
+        Rect expandedMovementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(
+                mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
+                bottomOffset);
+
+        updatePipSizeConstraints(normalBounds, aspectRatio);
+
+        // The extra offset does not really affect the movement bounds, but are applied based on the
+        // current state (ime showing, or shelf offset) when we need to actually shift
+        int extraOffset = Math.max(
+                mIsImeShowing ? mImeOffset : 0,
+                !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
+
+        // Update the movement bounds after doing the calculations based on the old movement bounds
+        // above
+        mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
+        mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
+        mDisplayRotation = displayRotation;
+        mInsetBounds.set(insetBounds);
+        updateMovementBounds();
+        mMovementBoundsExtraOffsets = extraOffset;
+
+        // If we have a deferred resize, apply it now
+        if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
+            mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
+                    mPipBoundsState.getNormalMovementBounds(), mPipBoundsState.getMovementBounds(),
+                    true /* immediate */);
+            mSavedSnapFraction = -1f;
+            mDeferResizeToNormalBoundsUntilRotation = -1;
+        }
+    }
+
+    /**
+     * Update the values for min/max allowed size of picture in picture window based on the aspect
+     * ratio.
+     * @param aspectRatio aspect ratio to use for the calculation of min/max size
+     */
+    public void updateMinMaxSize(float aspectRatio) {
+        updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
+                aspectRatio);
+    }
+
+    private void updatePipSizeConstraints(Rect normalBounds,
+            float aspectRatio) {
+        if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+            updatePinchResizeSizeConstraints(aspectRatio);
+        } else {
+            mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
+            mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
+                    mPipBoundsState.getExpandedBounds().height());
+        }
+    }
+
+    private void updatePinchResizeSizeConstraints(float aspectRatio) {
+        mPipBoundsState.updateMinMaxSize(aspectRatio);
+        mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+                mPipBoundsState.getMinSize().y);
+        mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+                mPipBoundsState.getMaxSize().y);
+    }
+
+    /**
+     * TODO Add appropriate description
+     */
+    public void onRegistrationChanged(boolean isRegistered) {
+        if (isRegistered) {
+            // Register the accessibility connection.
+        } else {
+            mAccessibilityManager.setPictureInPictureActionReplacingConnection(null);
+        }
+        if (!isRegistered && mTouchState.isUserInteracting()) {
+            // If the input consumer is unregistered while the user is interacting, then we may not
+            // get the final TOUCH_UP event, so clean up the dismiss target as well
+            mPipDismissTargetHandler.cleanUpDismissTarget();
+        }
+    }
+
+    private void onAccessibilityShowMenu() {
+        mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                true /* allowMenuTimeout */, willResizeMenu(),
+                shouldShowResizeHandle());
+    }
+
+    /**
+     * TODO Add appropriate description
+     */
+    public boolean handleTouchEvent(InputEvent inputEvent) {
+        // Skip any non motion events
+        if (!(inputEvent instanceof MotionEvent)) {
+            return true;
+        }
+
+        // do not process input event if not allowed
+        if (!mTouchState.getAllowInputEvents()) {
+            return true;
+        }
+
+        MotionEvent ev = (MotionEvent) inputEvent;
+        if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
+            // Initialize the touch state for the gesture, but immediately reset to invalidate the
+            // gesture
+            mTouchState.onTouchEvent(ev);
+            mTouchState.reset();
+            return true;
+        }
+
+        if (mPipResizeGestureHandler.hasOngoingGesture()) {
+            mGesture.cleanUpHighPerfSessionMaybe();
+            mPipDismissTargetHandler.hideDismissTargetMaybe();
+            return true;
+        }
+
+        if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
+                && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
+            // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+            // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+            // events it'll receive if the object is dragged out of the magnetic field.
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                mTouchState.onTouchEvent(ev);
+            }
+
+            // Continue tracking velocity when the object is in the magnetic field, since we want to
+            // respect touch input velocity if the object is dragged out and then flung.
+            mTouchState.addMovementToVelocityTracker(ev);
+
+            return true;
+        }
+
+        if (!mTouchState.isUserInteracting()) {
+            ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Waiting to start the entry animation, skip the motion event.", TAG);
+            return true;
+        }
+
+        // Update the touch state
+        mTouchState.onTouchEvent(ev);
+
+        boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                mGesture.onDown(mTouchState);
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (mGesture.onMove(mTouchState)) {
+                    break;
+                }
+
+                shouldDeliverToMenu = !mTouchState.isDragging();
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                // Update the movement bounds again if the state has changed since the user started
+                // dragging (ie. when the IME shows)
+                updateMovementBounds();
+
+                if (mGesture.onUp(mTouchState)) {
+                    break;
+                }
+            }
+            // Fall through to clean up
+            case MotionEvent.ACTION_CANCEL: {
+                shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
+                mTouchState.reset();
+                break;
+            }
+            case MotionEvent.ACTION_HOVER_ENTER: {
+                // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+                // on and changing MotionEvents into HoverEvents.
+                // Let's not enable menu show/hide for a11y services.
+                if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+                    mTouchState.removeHoverExitTimeoutCallback();
+                    mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                            false /* allowMenuTimeout */, false /* willResizeMenu */,
+                            shouldShowResizeHandle());
+                }
+            }
+            // Fall through
+            case MotionEvent.ACTION_HOVER_MOVE: {
+                if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
+                    sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+                    mSendingHoverAccessibilityEvents = true;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_HOVER_EXIT: {
+                // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+                // on and changing MotionEvents into HoverEvents.
+                // Let's not enable menu show/hide for a11y services.
+                if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+                    mTouchState.scheduleHoverExitTimeoutCallback();
+                }
+                if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
+                    sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+                    mSendingHoverAccessibilityEvents = false;
+                }
+                break;
+            }
+        }
+
+        shouldDeliverToMenu &= !mPipBoundsState.isStashed();
+
+        // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+        if (shouldDeliverToMenu) {
+            final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+            // Send the cancel event and cancel menu timeout if it starts to drag.
+            if (mTouchState.startedDragging()) {
+                cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+                mMenuController.pokeMenu();
+            }
+
+            mMenuController.handlePointerEvent(cloneEvent);
+            cloneEvent.recycle();
+        }
+
+        return true;
+    }
+
+    private void sendAccessibilityHoverEvent(int type) {
+        if (!mAccessibilityManager.isEnabled()) {
+            return;
+        }
+
+        AccessibilityEvent event = AccessibilityEvent.obtain(type);
+        event.setImportantForAccessibility(true);
+        event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
+        event.setWindowId(
+                AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+        mAccessibilityManager.sendAccessibilityEvent(event);
+    }
+
+    /**
+     * Called when the PiP menu state is in the process of animating/changing from one to another.
+     */
+    private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+        if (mMenuState == menuState && !resize) {
+            return;
+        }
+
+        if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
+            // Save the current snap fraction and if we do not drag or move the PiP, then
+            // we store back to this snap fraction.  Otherwise, we'll reset the snap
+            // fraction and snap to the closest edge.
+            if (resize) {
+                // PIP is too small to show the menu actions and thus needs to be resized to a
+                // size that can fit them all. Resize to the default size.
+                animateToNormalSize(callback);
+            }
+        } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
+            // Try and restore the PiP to the closest edge, using the saved snap fraction
+            // if possible
+            if (resize && !mPipResizeGestureHandler.isResizing()) {
+                if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+                    // This is a very special case: when the menu is expanded and visible,
+                    // navigating to another activity can trigger auto-enter PiP, and if the
+                    // revealed activity has a forced rotation set, then the controller will get
+                    // updated with the new rotation of the display. However, at the same time,
+                    // SystemUI will try to hide the menu by creating an animation to the normal
+                    // bounds which are now stale.  In such a case we defer the animation to the
+                    // normal bounds until after the next onMovementBoundsChanged() call to get the
+                    // bounds in the new orientation
+                    int displayRotation = mContext.getDisplay().getRotation();
+                    if (mDisplayRotation != displayRotation) {
+                        mDeferResizeToNormalBoundsUntilRotation = displayRotation;
+                    }
+                }
+
+                if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+                    animateToUnexpandedState(getUserResizeBounds());
+                }
+            } else {
+                mSavedSnapFraction = -1f;
+            }
+        }
+    }
+
+    private void setMenuState(int menuState) {
+        mMenuState = menuState;
+        updateMovementBounds();
+        // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
+        // as well, or it can't handle a11y focus and pip menu can't perform any action.
+        onRegistrationChanged(menuState == MENU_STATE_NONE);
+        if (menuState == MENU_STATE_NONE) {
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
+        } else if (menuState == MENU_STATE_FULL) {
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
+        }
+    }
+
+    private void animateToMaximizedState(Runnable callback) {
+        Rect maxMovementBounds = new Rect();
+        Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+                mPipBoundsState.getMaxSize().y);
+        mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
+                mIsImeShowing ? mImeHeight : 0);
+        mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
+                mPipBoundsState.getMovementBounds(), maxMovementBounds,
+                callback);
+    }
+
+    private void animateToNormalSize(Runnable callback) {
+        // Save the current bounds as the user-resize bounds.
+        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+
+        final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
+        final Rect normalBounds = mPipBoundsState.getNormalBounds();
+        final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
+                minMenuSize);
+        Rect restoredMovementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(destBounds,
+                mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+        mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
+                mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+    }
+
+    private void animateToUnexpandedState(Rect restoreBounds) {
+        Rect restoredMovementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
+                mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+        mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
+                restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+        mSavedSnapFraction = -1f;
+    }
+
+    private void animateToUnStashedState() {
+        final Rect pipBounds = mPipBoundsState.getBounds();
+        final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+        final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+        unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+                : mInsetBounds.right - pipBounds.width();
+        unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+                : mInsetBounds.right;
+        mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+    }
+
+    /**
+     * @return the motion helper.
+     */
+    public PipMotionHelper getMotionHelper() {
+        return mMotionHelper;
+    }
+
+    @VisibleForTesting
+    public PipResizeGestureHandler getPipResizeGestureHandler() {
+        return mPipResizeGestureHandler;
+    }
+
+    @VisibleForTesting
+    public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
+        mPipResizeGestureHandler = pipResizeGestureHandler;
+    }
+
+    @VisibleForTesting
+    public void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
+        mMotionHelper = pipMotionHelper;
+    }
+
+    Rect getUserResizeBounds() {
+        return mPipResizeGestureHandler.getUserResizeBounds();
+    }
+
+    /**
+     * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
+     */
+    void setUserResizeBounds(Rect bounds) {
+        mPipResizeGestureHandler.setUserResizeBounds(bounds);
+    }
+
+    /**
+     * Gesture controlling normal movement of the PIP.
+     */
+    private class DefaultPipTouchGesture extends PipTouchGesture {
+        private final Point mStartPosition = new Point();
+        private final PointF mDelta = new PointF();
+        private boolean mShouldHideMenuAfterFling;
+
+        @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+        private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+        @Override
+        public void cleanUpHighPerfSessionMaybe() {
+            if (mPipHighPerfSession != null) {
+                // Close the high perf session once pointer interactions are over;
+                mPipHighPerfSession.close();
+                mPipHighPerfSession = null;
+            }
+        }
+
+        @Override
+        public void onDown(PipTouchState touchState) {
+            if (!touchState.isUserInteracting()) {
+                return;
+            }
+
+            if (mPipPerfHintController != null) {
+                // Cache the PiP high perf session to close it upon touch up.
+                mPipHighPerfSession = mPipPerfHintController.startSession(
+                        this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown");
+            }
+
+            Rect bounds = getPossiblyMotionBounds();
+            mDelta.set(0f, 0f);
+            mStartPosition.set(bounds.left, bounds.top);
+            mMovementWithinDismiss = touchState.getDownTouchPosition().y
+                    >= mPipBoundsState.getMovementBounds().bottom;
+            mMotionHelper.setSpringingToTouch(false);
+            // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl());
+
+            // If the menu is still visible then just poke the menu
+            // so that it will timeout after the user stops touching it
+            if (mMenuState != MENU_STATE_NONE && !mPipBoundsState.isStashed()) {
+                mMenuController.pokeMenu();
+            }
+        }
+
+        @Override
+        public boolean onMove(PipTouchState touchState) {
+            if (!touchState.isUserInteracting()) {
+                return false;
+            }
+
+            if (touchState.startedDragging()) {
+                mSavedSnapFraction = -1f;
+                mPipDismissTargetHandler.showDismissTargetMaybe();
+            }
+
+            if (touchState.isDragging()) {
+                mPipBoundsState.setHasUserMovedPip(true);
+
+                // Move the pinned stack freely
+                final PointF lastDelta = touchState.getLastTouchDelta();
+                float lastX = mStartPosition.x + mDelta.x;
+                float lastY = mStartPosition.y + mDelta.y;
+                float left = lastX + lastDelta.x;
+                float top = lastY + lastDelta.y;
+
+                // Add to the cumulative delta after bounding the position
+                mDelta.x += left - lastX;
+                mDelta.y += top - lastY;
+
+                mTmpBounds.set(getPossiblyMotionBounds());
+                mTmpBounds.offsetTo((int) left, (int) top);
+                mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
+
+                final PointF curPos = touchState.getLastTouchPosition();
+                if (mMovementWithinDismiss) {
+                    // Track if movement remains near the bottom edge to identify swipe to dismiss
+                    mMovementWithinDismiss = curPos.y >= mPipBoundsState.getMovementBounds().bottom;
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onUp(PipTouchState touchState) {
+            mPipDismissTargetHandler.hideDismissTargetMaybe();
+            mPipDismissTargetHandler.setTaskLeash(null);
+
+            if (!touchState.isUserInteracting()) {
+                return false;
+            }
+
+            final PointF vel = touchState.getVelocity();
+
+            if (touchState.isDragging()) {
+                if (mMenuState != MENU_STATE_NONE) {
+                    // If the menu is still visible, then just poke the menu so that
+                    // it will timeout after the user stops touching it
+                    mMenuController.showMenu(mMenuState, mPipBoundsState.getBounds(),
+                            true /* allowMenuTimeout */, willResizeMenu(),
+                            shouldShowResizeHandle());
+                }
+                mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE;
+
+                // Reset the touch state on up before the fling settles
+                mTouchState.reset();
+                if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
+                    mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
+                } else {
+                    if (mPipBoundsState.isStashed()) {
+                        // Reset stashed state if previously stashed
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    }
+                    mMotionHelper.flingToSnapTarget(vel.x, vel.y,
+                            this::flingEndAction /* endAction */);
+                }
+            } else if (mTouchState.isDoubleTap() && !mPipBoundsState.isStashed()
+                    && mMenuState != MENU_STATE_FULL) {
+                // If using pinch to zoom, double-tap functions as resizing between max/min size
+                if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+                    final boolean toExpand = mPipBoundsState.getBounds().width()
+                            < mPipBoundsState.getMaxSize().x
+                            && mPipBoundsState.getBounds().height()
+                            < mPipBoundsState.getMaxSize().y;
+                    if (mMenuController.isMenuVisible()) {
+                        mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+                    }
+
+                    // the size to toggle to after a double tap
+                    int nextSize = PipDoubleTapHelper
+                            .nextSizeSpec(mPipBoundsState, getUserResizeBounds());
+
+                    // actually toggle to the size chosen
+                    if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
+                        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+                        animateToMaximizedState(null);
+                    } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
+                        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+                        animateToNormalSize(null);
+                    } else {
+                        animateToUnexpandedState(getUserResizeBounds());
+                    }
+                } else {
+                    // Expand to fullscreen if this is a double tap
+                    // the PiP should be frozen until the transition ends
+                    setTouchEnabled(false);
+                    mMotionHelper.expandLeavePip(false /* skipAnimation */);
+                }
+            } else if (mMenuState != MENU_STATE_FULL) {
+                if (mPipBoundsState.isStashed()) {
+                    // Unstash immediately if stashed, and don't wait for the double tap timeout
+                    animateToUnStashedState();
+                    mPipUiEventLogger.log(
+                            PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+                    mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    mTouchState.removeDoubleTapTimeoutCallback();
+                } else if (!mTouchState.isWaitingForDoubleTap()) {
+                    // User has stalled long enough for this not to be a drag or a double tap,
+                    // just expand the menu
+                    mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                            true /* allowMenuTimeout */, willResizeMenu(),
+                            shouldShowResizeHandle());
+                } else {
+                    // Next touch event _may_ be the second tap for the double-tap, schedule a
+                    // fallback runnable to trigger the menu if no touch event occurs before the
+                    // next tap
+                    mTouchState.scheduleDoubleTapTimeoutCallback();
+                }
+            }
+            cleanUpHighPerfSessionMaybe();
+            return true;
+        }
+
+        private void stashEndAction() {
+            if (mPipBoundsState.getBounds().left < 0
+                    && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+                mPipUiEventLogger.log(
+                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
+                mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+            } else if (mPipBoundsState.getBounds().left >= 0
+                    && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+                mPipUiEventLogger.log(
+                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
+                mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+            }
+            mMenuController.hideMenu();
+        }
+
+        private void flingEndAction() {
+            if (mShouldHideMenuAfterFling) {
+                // If the menu is not visible, then we can still be showing the activity for the
+                // dismiss overlay, so just finish it after the animation completes
+                mMenuController.hideMenu();
+            }
+        }
+
+        private boolean shouldStash(PointF vel, Rect motionBounds) {
+            final boolean flingToLeft = vel.x < -mStashVelocityThreshold;
+            final boolean flingToRight = vel.x > mStashVelocityThreshold;
+            final int offset = motionBounds.width() / 2;
+            final boolean droppingOnLeft =
+                    motionBounds.left < mPipBoundsState.getDisplayBounds().left - offset;
+            final boolean droppingOnRight =
+                    motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset;
+
+            // Do not allow stash if the destination edge contains display cutout. We only
+            // compare the left and right edges since we do not allow stash on top / bottom.
+            final DisplayCutout displayCutout =
+                    mPipBoundsState.getDisplayLayout().getDisplayCutout();
+            if (displayCutout != null) {
+                if ((flingToLeft || droppingOnLeft)
+                        && !displayCutout.getBoundingRectLeft().isEmpty()) {
+                    return false;
+                } else if ((flingToRight || droppingOnRight)
+                        && !displayCutout.getBoundingRectRight().isEmpty()) {
+                    return false;
+                }
+            }
+
+            // If user flings the PIP window above the minimum velocity, stash PIP.
+            // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite
+            // edge.
+            final boolean stashFromFlingToEdge =
+                    (flingToLeft && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT)
+                    || (flingToRight && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT);
+
+            // If User releases the PIP window while it's out of the display bounds, put
+            // PIP into stashed mode.
+            final boolean stashFromDroppingOnEdge = droppingOnLeft || droppingOnRight;
+
+            return stashFromFlingToEdge || stashFromDroppingOnEdge;
+        }
+    }
+
+    /**
+     * Updates the current movement bounds based on whether the menu is currently visible and
+     * resized.
+     */
+    private void updateMovementBounds() {
+        mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
+                mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+        mMotionHelper.onMovementBoundsChanged();
+    }
+
+    private Rect getMovementBounds(Rect curBounds) {
+        Rect movementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+                movementBounds, mIsImeShowing ? mImeHeight : 0);
+        return movementBounds;
+    }
+
+    /**
+     * @return {@code true} if the menu should be resized on tap because app explicitly specifies
+     * PiP window size that is too small to hold all the actions.
+     */
+    private boolean willResizeMenu() {
+        if (!mEnableResize) {
+            return false;
+        }
+        final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+        if (estimatedMinMenuSize == null) {
+            ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Failed to get estimated menu size", TAG);
+            return false;
+        }
+        final Rect currentBounds = mPipBoundsState.getBounds();
+        return currentBounds.width() < estimatedMinMenuSize.getWidth()
+                || currentBounds.height() < estimatedMinMenuSize.getHeight();
+    }
+
+    /**
+     * Returns the PIP bounds if we're not in the middle of a motion operation, or the current,
+     * temporary motion bounds otherwise.
+     */
+    Rect getPossiblyMotionBounds() {
+        return mPipBoundsState.getMotionBoundsState().isInMotion()
+                ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion()
+                : mPipBoundsState.getBounds();
+    }
+
+    void setOhmOffset(int offset) {
+        mPipResizeGestureHandler.setOhmOffset(offset);
+    }
+
+    /**
+     * Dumps the {@link PipTouchHandler} state.
+     */
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mMenuState=" + mMenuState);
+        pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
+        pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+        pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+        pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+        pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
+        pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
+        mPipBoundsAlgorithm.dump(pw, innerPrefix);
+        mTouchState.dump(pw, innerPrefix);
+        if (mPipResizeGestureHandler != null) {
+            mPipResizeGestureHandler.dump(pw, innerPrefix);
+        }
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 5762197..6aad4e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,9 +18,14 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.graphics.Rect;
+import android.os.Bundle;
+import android.window.RemoteTransition;
 
+import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 
@@ -72,6 +77,12 @@
         }
     }
 
+    /** Launches a pair of tasks into splitscreen */
+    void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition,
+            @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+            InstanceId instanceId);
+
     /** Registers listener that gets split screen callback. */
     void registerSplitScreenListener(@NonNull SplitScreenListener listener,
             @NonNull Executor executor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 088bb48..547457b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -506,6 +506,15 @@
         return mStageCoordinator.getActivateSplitPosition(taskInfo);
     }
 
+    /** Start two tasks in parallel as a splitscreen pair. */
+    public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition,
+            @PersistentSnapPosition int snapPosition,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        mStageCoordinator.startTasks(taskId1, options1, taskId2, options2, splitPosition,
+                snapPosition, remoteTransition, instanceId);
+    }
+
     /**
      * Move a task to split select
      * @param taskInfo the task being moved to split select
@@ -1120,6 +1129,15 @@
         };
 
         @Override
+        public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+                @Nullable Bundle options2, int splitPosition, int snapPosition,
+                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+            mMainExecutor.execute(() -> SplitScreenController.this.startTasks(
+                    taskId1, options1, taskId2, options2, splitPosition, snapPosition,
+                    remoteTransition, instanceId));
+        }
+
+        @Override
         public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
             if (mExecutors.containsKey(listener)) return;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index c26604a..7c2ba45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -293,7 +293,13 @@
     @Override
     public void onFoldStateChanged(boolean isFolded) {
         if (isFolded) {
+            // Reset unfold animation finished flag on folding, so it could be used next time
+            // when we unfold the device as an indication that animation hasn't finished yet
             mAnimationFinished = false;
+
+            // If we are currently animating unfold animation we should finish it because
+            // the animation might not start and finish as the device was folded
+            finishTransitionIfNeeded();
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index b94989d..12e395d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -24,6 +24,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.helpers.WindowUtils
 import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -143,6 +144,10 @@
         }
     }
 
+    @FlakyTest(bugId = 293133362)
+    @Test
+    override fun entireScreenCovered() = super.entireScreenCovered()
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 2ff1ddd..9c623bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -120,6 +120,8 @@
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
 
+    private CrossActivityBackAnimation mCrossActivityBackAnimation;
+    private CrossTaskBackAnimation mCrossTaskBackAnimation;
     private ShellBackAnimationRegistry mShellBackAnimationRegistry;
 
     @Before
@@ -133,11 +135,11 @@
                 ANIMATION_ENABLED);
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
+        mCrossActivityBackAnimation = new CrossActivityBackAnimation(mContext, mAnimationBackground,
+                mRootTaskDisplayAreaOrganizer);
+        mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
         mShellBackAnimationRegistry =
-                new ShellBackAnimationRegistry(
-                        new CrossActivityBackAnimation(
-                                mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer),
-                        new CrossTaskBackAnimation(mContext, mAnimationBackground),
+                new ShellBackAnimationRegistry(mCrossActivityBackAnimation, mCrossTaskBackAnimation,
                         /* dialogCloseAnimation= */ null,
                         new CustomizeActivityAnimation(mContext, mAnimationBackground),
                         /* defaultBackToHomeAnimation= */ null);
@@ -576,16 +578,14 @@
 
     @Test
     public void testBackToActivity() throws RemoteException {
-        final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(
-                mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer);
-        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
+        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                mCrossActivityBackAnimation.getRunner());
     }
 
     @Test
     public void testBackToTask() throws RemoteException {
-        final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
-                mAnimationBackground);
-        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
+        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK,
+                mCrossTaskBackAnimation.getRunner());
     }
 
     private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 48e396a..6be411d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1222,6 +1222,19 @@
         assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
     }
 
+    @Test
+    public void setSelectedBubbleAndExpandStack() {
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        mBubbleData.setListener(mListener);
+
+        mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1);
+
+        verifyUpdateReceived();
+        assertSelectionChangedTo(mBubbleA1);
+        assertExpandedChangedTo(true);
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index c5e229f..acc0bce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -298,6 +298,32 @@
     }
 
     @Test
+    public void fold_animationInProgress_finishesTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        // Unfold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        // Start animation but don't finish it
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeProgress(0.5f);
+
+        // Fold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true);
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    @Test
     public void mergeAnimation_eatsDisplayOnlyTransitions() {
         TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
         mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 02fa5f2..29bb1b9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -79,13 +79,13 @@
     include_dirs: [
         "external/skia/include/private",
         "external/skia/src/core",
+        "external/skia/src/utils",
     ],
 
     target: {
         android: {
             include_dirs: [
                 "external/skia/src/image",
-                "external/skia/src/utils",
                 "external/skia/src/gpu",
                 "external/skia/src/shaders",
             ],
@@ -530,7 +530,9 @@
         "effects/GainmapRenderer.cpp",
         "pipeline/skia/BackdropFilterDrawable.cpp",
         "pipeline/skia/HolePunch.cpp",
+        "pipeline/skia/SkiaCpuPipeline.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
+        "pipeline/skia/SkiaPipeline.cpp",
         "pipeline/skia/SkiaRecordingCanvas.cpp",
         "pipeline/skia/StretchMask.cpp",
         "pipeline/skia/RenderNodeDrawable.cpp",
@@ -569,6 +571,7 @@
         "HWUIProperties.sysprop",
         "Interpolator.cpp",
         "JankTracker.cpp",
+        "LayerUpdateQueue.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
         "Mesh.cpp",
@@ -605,9 +608,9 @@
                 "pipeline/skia/GLFunctorDrawable.cpp",
                 "pipeline/skia/LayerDrawable.cpp",
                 "pipeline/skia/ShaderCache.cpp",
+                "pipeline/skia/SkiaGpuPipeline.cpp",
                 "pipeline/skia/SkiaMemoryTracer.cpp",
                 "pipeline/skia/SkiaOpenGLPipeline.cpp",
-                "pipeline/skia/SkiaPipeline.cpp",
                 "pipeline/skia/SkiaProfileRenderer.cpp",
                 "pipeline/skia/SkiaVulkanPipeline.cpp",
                 "pipeline/skia/VkFunctorDrawable.cpp",
@@ -631,7 +634,6 @@
                 "DeferredLayerUpdater.cpp",
                 "HardwareBitmapUploader.cpp",
                 "Layer.cpp",
-                "LayerUpdateQueue.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
                 "TreeInfo.cpp",
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ec53070..c1510d9 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -242,7 +242,7 @@
 
 enum class OverdrawColorSet { Default = 0, Deuteranomaly };
 
-enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
+enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 };
 
 enum class StretchEffectBehavior {
     ShaderHWUI,   // Stretch shader in HWUI only, matrix scale in SF
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
new file mode 100644
index 0000000..5bbbc10
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open 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 "pipeline/skia/SkiaCpuPipeline.h"
+
+#include <system/window.h>
+
+#include "DeviceInfo.h"
+#include "LightingInfo.h"
+#include "renderthread/Frame.h"
+#include "utils/Color.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+    // Render all layers that need to be updated, in order.
+    for (size_t i = 0; i < layers.entries().size(); i++) {
+        RenderNode* layerNode = layers.entries()[i].renderNode.get();
+        // only schedule repaint if node still on layer - possible it may have been
+        // removed during a dropped frame, but layers may still remain scheduled so
+        // as not to lose info on what portion is damaged
+        if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+            continue;
+        }
+        bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage);
+        if (!rendered) {
+            return;
+        }
+    }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node,
+                                          const DamageAccumulator& damageAccumulator,
+                                          ErrorHandler* errorHandler) {
+    // compute the size of the surface (i.e. texture) to be allocated for this layer
+    const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+    const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+    SkSurface* layer = node->getLayerSurface();
+    if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+        SkImageInfo info;
+        info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+                                 kPremul_SkAlphaType, getSurfaceColorSpace());
+        SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+        node->setLayerSurface(SkSurfaces::Raster(info, &props));
+        if (node->getLayerSurface()) {
+            // update the transform in window of the layer to reset its origin wrt light source
+            // position
+            Matrix4 windowTransform;
+            damageAccumulator.computeCurrentTransform(&windowTransform);
+            node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+        } else {
+            String8 cachesOutput;
+            mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+                                                         &mRenderThread.renderState());
+            ALOGE("%s", cachesOutput.c_str());
+            if (errorHandler) {
+                std::ostringstream err;
+                err << "Unable to create layer for " << node->getName();
+                const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+                err << ", size " << info.width() << "x" << info.height() << " max size "
+                    << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+                    << (int)(mRenderThread.getGrContext() != nullptr);
+                errorHandler->onError(err.str());
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+MakeCurrentResult SkiaCpuPipeline::makeCurrent() {
+    return MakeCurrentResult::AlreadyCurrent;
+}
+
+Frame SkiaCpuPipeline::getFrame() {
+    return Frame(mSurface->width(), mSurface->height(), 0);
+}
+
+IRenderPipeline::DrawResult SkiaCpuPipeline::draw(
+        const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+        const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+        const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+        const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
+    LightingInfo::updateLighting(lightGeometry, lightInfo);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface,
+                SkMatrix::I());
+    return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
+}
+
+bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+    if (surface) {
+        ANativeWindowBuffer* buffer;
+        surface->dequeueBuffer(surface, &buffer, nullptr);
+        int width, height;
+        surface->query(surface, NATIVE_WINDOW_WIDTH, &width);
+        surface->query(surface, NATIVE_WINDOW_HEIGHT, &height);
+        SkImageInfo imageInfo =
+                SkImageInfo::Make(width, height, mSurfaceColorType,
+                                  SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace);
+        size_t widthBytes = width * imageInfo.bytesPerPixel();
+        void* pixels = buffer->reserved[0];
+        mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes);
+    } else {
+        mSurface = sk_sp<SkSurface>();
+    }
+    return true;
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
new file mode 100644
index 0000000..5a1014c
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open 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 "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaCpuPipeline : public SkiaPipeline {
+public:
+    SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+    ~SkiaCpuPipeline() {}
+
+    bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+    bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+    void unpinImages() override {}
+
+    // If the given node didn't have a layer surface, or had one of the wrong size, this method
+    // creates a new one and returns true. Otherwise does nothing and returns false.
+    bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+                             ErrorHandler* errorHandler) override;
+    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+    void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+    bool hasHardwareBuffer() override { return false; }
+
+    renderthread::MakeCurrentResult makeCurrent() override;
+    renderthread::Frame getFrame() override;
+    renderthread::IRenderPipeline::DrawResult draw(
+            const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+            const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+            const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+            const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
+    bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                     bool* requireSwap) override {
+        return false;
+    }
+    DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+    bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+    [[nodiscard]] android::base::unique_fd flush() override {
+        return android::base::unique_fd(-1);
+    };
+    void onStop() override {}
+    bool isSurfaceReady() override { return mSurface.get() != nullptr; }
+    bool isContextReady() override { return true; }
+
+    const SkM44& getPixelSnapMatrix() const override {
+        static const SkM44 sSnapMatrix = SkM44();
+        return sSnapMatrix;
+    }
+
+private:
+    sk_sp<SkSurface> mSurface;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
new file mode 100644
index 0000000..7bfbfdc
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+#include <SkImageAndroid.h>
+#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {}
+
+SkiaGpuPipeline::~SkiaGpuPipeline() {
+    unpinImages();
+}
+
+void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+    sk_sp<GrDirectContext> cachedContext;
+
+    // Render all layers that need to be updated, in order.
+    for (size_t i = 0; i < layers.entries().size(); i++) {
+        RenderNode* layerNode = layers.entries()[i].renderNode.get();
+        // only schedule repaint if node still on layer - possible it may have been
+        // removed during a dropped frame, but layers may still remain scheduled so
+        // as not to lose info on what portion is damaged
+        if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+            continue;
+        }
+        bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage);
+        if (!rendered) {
+            return;
+        }
+        // cache the current context so that we can defer flushing it until
+        // either all the layers have been rendered or the context changes
+        GrDirectContext* currentContext =
+                GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
+        if (cachedContext.get() != currentContext) {
+            if (cachedContext.get()) {
+                ATRACE_NAME("flush layers (context changed)");
+                cachedContext->flushAndSubmit();
+            }
+            cachedContext.reset(SkSafeRef(currentContext));
+        }
+    }
+    if (cachedContext.get()) {
+        ATRACE_NAME("flush layers");
+        cachedContext->flushAndSubmit();
+    }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node,
+                                          const DamageAccumulator& damageAccumulator,
+                                          ErrorHandler* errorHandler) {
+    // compute the size of the surface (i.e. texture) to be allocated for this layer
+    const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+    const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+    SkSurface* layer = node->getLayerSurface();
+    if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+        SkImageInfo info;
+        info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+                                 kPremul_SkAlphaType, getSurfaceColorSpace());
+        SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+        SkASSERT(mRenderThread.getGrContext() != nullptr);
+        node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                                       skgpu::Budgeted::kYes, info, 0,
+                                                       this->getSurfaceOrigin(), &props));
+        if (node->getLayerSurface()) {
+            // update the transform in window of the layer to reset its origin wrt light source
+            // position
+            Matrix4 windowTransform;
+            damageAccumulator.computeCurrentTransform(&windowTransform);
+            node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+        } else {
+            String8 cachesOutput;
+            mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+                                                         &mRenderThread.renderState());
+            ALOGE("%s", cachesOutput.c_str());
+            if (errorHandler) {
+                std::ostringstream err;
+                err << "Unable to create layer for " << node->getName();
+                const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+                err << ", size " << info.width() << "x" << info.height() << " max size "
+                    << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+                    << (int)(mRenderThread.getGrContext() != nullptr);
+                errorHandler->onError(err.str());
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
+    if (!mRenderThread.getGrContext()) {
+        ALOGD("Trying to pin an image with an invalid GrContext");
+        return false;
+    }
+    for (SkImage* image : mutableImages) {
+        if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
+            mPinnedImages.emplace_back(sk_ref_sp(image));
+        } else {
+            return false;
+        }
+    }
+    return true;
+}
+
+void SkiaGpuPipeline::unpinImages() {
+    for (auto& image : mPinnedImages) {
+        skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
+    }
+    mPinnedImages.clear();
+}
+
+void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+    GrDirectContext* context = thread.getGrContext();
+    if (context && !bitmap->isHardware()) {
+        ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
+        auto image = bitmap->makeImage();
+        if (image.get()) {
+            skgpu::ganesh::PinAsTexture(context, image.get());
+            skgpu::ganesh::UnpinTexture(context, image.get());
+            // A submit is necessary as there may not be a frame coming soon, so without a call
+            // to submit these texture uploads can just sit in the queue building up until
+            // we run out of RAM
+            context->flushAndSubmit();
+        }
+    }
+}
+
+sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface(
+        const renderthread::HardwareBufferRenderParams& bufferParams) {
+    auto bufferColorSpace = bufferParams.getColorSpace();
+    if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+        !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+        mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
+                mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+                bufferColorSpace, nullptr, true);
+        mBufferColorSpace = bufferColorSpace;
+    }
+    return mBufferSurface;
+}
+
+void SkiaGpuPipeline::dumpResourceCacheUsage() const {
+    int resources;
+    size_t bytes;
+    mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
+    size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
+
+    SkString log("Resource Cache Usage:\n");
+    log.appendf("%8d items\n", resources);
+    log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
+                bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
+
+    ALOGD("%s", log.c_str());
+}
+
+void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+    if (mHardwareBuffer) {
+        AHardwareBuffer_release(mHardwareBuffer);
+        mHardwareBuffer = nullptr;
+    }
+
+    if (buffer) {
+        AHardwareBuffer_acquire(buffer);
+        mHardwareBuffer = buffer;
+    }
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c8d5987..e4b1f91 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -14,25 +14,25 @@
  * limitations under the License.
  */
 
-#include "SkiaOpenGLPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
 
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/gl/GrGLTypes.h>
 #include <GrBackendSurface.h>
 #include <SkBlendMode.h>
 #include <SkImageInfo.h>
 #include <cutils/properties.h>
 #include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
 #include <strings.h>
 
 #include "DeferredLayerUpdater.h"
 #include "FrameInfo.h"
-#include "LayerDrawable.h"
 #include "LightingInfo.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
 #include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
 #include "private/hwui/DrawGlInfo.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/EglManager.h"
@@ -47,7 +47,7 @@
 namespace skiapipeline {
 
 SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
-        : SkiaPipeline(thread), mEglManager(thread.eglManager()) {
+        : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) {
     thread.renderState().registerContextCallback(this);
 }
 
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 99469d1..34932b1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaPipeline.h"
 
-#include <include/android/SkSurfaceAndroid.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/encode/SkPngEncoder.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
 #include <SkColorSpace.h>
@@ -40,6 +37,9 @@
 #include <SkTypeface.h>
 #include <android-base/properties.h>
 #include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/encode/SkPngEncoder.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <unistd.h>
 
 #include <sstream>
@@ -62,37 +62,13 @@
     setSurfaceColorProperties(mColorMode);
 }
 
-SkiaPipeline::~SkiaPipeline() {
-    unpinImages();
-}
+SkiaPipeline::~SkiaPipeline() {}
 
 void SkiaPipeline::onDestroyHardwareResources() {
     unpinImages();
     mRenderThread.cacheManager().trimStaleResources();
 }
 
-bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
-    if (!mRenderThread.getGrContext()) {
-        ALOGD("Trying to pin an image with an invalid GrContext");
-        return false;
-    }
-    for (SkImage* image : mutableImages) {
-        if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
-            mPinnedImages.emplace_back(sk_ref_sp(image));
-        } else {
-            return false;
-        }
-    }
-    return true;
-}
-
-void SkiaPipeline::unpinImages() {
-    for (auto& image : mPinnedImages) {
-        skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
-    }
-    mPinnedImages.clear();
-}
-
 void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
                                 LayerUpdateQueue* layerUpdateQueue, bool opaque,
                                 const LightInfo& lightInfo) {
@@ -102,136 +78,48 @@
     layerUpdateQueue->clear();
 }
 
-void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
-    sk_sp<GrDirectContext> cachedContext;
-
-    // Render all layers that need to be updated, in order.
-    for (size_t i = 0; i < layers.entries().size(); i++) {
-        RenderNode* layerNode = layers.entries()[i].renderNode.get();
-        // only schedule repaint if node still on layer - possible it may have been
-        // removed during a dropped frame, but layers may still remain scheduled so
-        // as not to lose info on what portion is damaged
-        if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
-            continue;
-        }
-        SkASSERT(layerNode->getLayerSurface());
-        SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
-        if (!displayList || displayList->isEmpty()) {
-            ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
-            return;
-        }
-
-        const Rect& layerDamage = layers.entries()[i].damage;
-
-        SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-
-        int saveCount = layerCanvas->save();
-        SkASSERT(saveCount == 1);
-
-        layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-
-        // TODO: put localized light center calculation and storage to a drawable related code.
-        // It does not seem right to store something localized in a global state
-        // fix here and in recordLayers
-        const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
-        Vector3 transformedLightCenter(savedLightCenter);
-        // map current light center into RenderNode's coordinate space
-        layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
-        LightingInfo::setLightCenterRaw(transformedLightCenter);
-
-        const RenderProperties& properties = layerNode->properties();
-        const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
-        if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
-            return;
-        }
-
-        ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
-                      bounds.height());
-
-        layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
-        layerCanvas->clear(SK_ColorTRANSPARENT);
-
-        RenderNodeDrawable root(layerNode, layerCanvas, false);
-        root.forceDraw(layerCanvas);
-        layerCanvas->restoreToCount(saveCount);
-
-        LightingInfo::setLightCenterRaw(savedLightCenter);
-
-        // cache the current context so that we can defer flushing it until
-        // either all the layers have been rendered or the context changes
-        GrDirectContext* currentContext =
-            GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
-        if (cachedContext.get() != currentContext) {
-            if (cachedContext.get()) {
-                ATRACE_NAME("flush layers (context changed)");
-                cachedContext->flushAndSubmit();
-            }
-            cachedContext.reset(SkSafeRef(currentContext));
-        }
+bool SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) {
+    SkASSERT(layerNode->getLayerSurface());
+    SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
+    if (!displayList || displayList->isEmpty()) {
+        ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+        return false;
     }
 
-    if (cachedContext.get()) {
-        ATRACE_NAME("flush layers");
-        cachedContext->flushAndSubmit();
-    }
-}
+    SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
 
-bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
-                                       ErrorHandler* errorHandler) {
-    // compute the size of the surface (i.e. texture) to be allocated for this layer
-    const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
-    const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+    int saveCount = layerCanvas->save();
+    SkASSERT(saveCount == 1);
 
-    SkSurface* layer = node->getLayerSurface();
-    if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
-        SkImageInfo info;
-        info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
-                                 kPremul_SkAlphaType, getSurfaceColorSpace());
-        SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
-        SkASSERT(mRenderThread.getGrContext() != nullptr);
-        node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
-                                                       skgpu::Budgeted::kYes, info, 0,
-                                                       this->getSurfaceOrigin(), &props));
-        if (node->getLayerSurface()) {
-            // update the transform in window of the layer to reset its origin wrt light source
-            // position
-            Matrix4 windowTransform;
-            damageAccumulator.computeCurrentTransform(&windowTransform);
-            node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
-        } else {
-            String8 cachesOutput;
-            mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
-                                                         &mRenderThread.renderState());
-            ALOGE("%s", cachesOutput.c_str());
-            if (errorHandler) {
-                std::ostringstream err;
-                err << "Unable to create layer for " << node->getName();
-                const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
-                err << ", size " << info.width() << "x" << info.height() << " max size "
-                    << maxTextureSize << " color type " << (int)info.colorType() << " has context "
-                    << (int)(mRenderThread.getGrContext() != nullptr);
-                errorHandler->onError(err.str());
-            }
-        }
-        return true;
-    }
-    return false;
-}
+    layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
 
-void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
-    GrDirectContext* context = thread.getGrContext();
-    if (context && !bitmap->isHardware()) {
-        ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
-        auto image = bitmap->makeImage();
-        if (image.get()) {
-            skgpu::ganesh::PinAsTexture(context, image.get());
-            skgpu::ganesh::UnpinTexture(context, image.get());
-            // A submit is necessary as there may not be a frame coming soon, so without a call
-            // to submit these texture uploads can just sit in the queue building up until
-            // we run out of RAM
-            context->flushAndSubmit();
-        }
+    // TODO: put localized light center calculation and storage to a drawable related code.
+    // It does not seem right to store something localized in a global state
+    // fix here and in recordLayers
+    const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+    Vector3 transformedLightCenter(savedLightCenter);
+    // map current light center into RenderNode's coordinate space
+    layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+    LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+    const RenderProperties& properties = layerNode->properties();
+    const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+    if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+        return false;
     }
+
+    ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+                  bounds.height());
+
+    layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+    layerCanvas->clear(SK_ColorTRANSPARENT);
+
+    RenderNodeDrawable root(layerNode, layerCanvas, false);
+    root.forceDraw(layerCanvas);
+    layerCanvas->restoreToCount(saveCount);
+
+    LightingInfo::setLightCenterRaw(savedLightCenter);
+    return true;
 }
 
 static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
@@ -599,45 +487,6 @@
     }
 }
 
-void SkiaPipeline::dumpResourceCacheUsage() const {
-    int resources;
-    size_t bytes;
-    mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
-    size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
-
-    SkString log("Resource Cache Usage:\n");
-    log.appendf("%8d items\n", resources);
-    log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
-                bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
-
-    ALOGD("%s", log.c_str());
-}
-
-void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
-    if (mHardwareBuffer) {
-        AHardwareBuffer_release(mHardwareBuffer);
-        mHardwareBuffer = nullptr;
-    }
-
-    if (buffer) {
-        AHardwareBuffer_acquire(buffer);
-        mHardwareBuffer = buffer;
-    }
-}
-
-sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
-        const renderthread::HardwareBufferRenderParams& bufferParams) {
-    auto bufferColorSpace = bufferParams.getColorSpace();
-    if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
-        !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
-        mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
-                mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
-                bufferColorSpace, nullptr, true);
-        mBufferColorSpace = bufferColorSpace;
-    }
-    return mBufferSurface;
-}
-
 void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
     mColorMode = colorMode;
     switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index befee89..cf14b1f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -42,18 +42,9 @@
 
     void onDestroyHardwareResources() override;
 
-    bool pinImages(std::vector<SkImage*>& mutableImages) override;
-    bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
-    void unpinImages() override;
-
     void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
                       bool opaque, const LightInfo& lightInfo) override;
 
-    // If the given node didn't have a layer surface, or had one of the wrong size, this method
-    // creates a new one and returns true. Otherwise does nothing and returns false.
-    bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
-                             ErrorHandler* errorHandler) override;
-
     void setSurfaceColorProperties(ColorMode colorMode) override;
     SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
     sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
@@ -63,9 +54,8 @@
                      const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                      const SkMatrix& preTransform);
 
-    static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
-
-    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
+    bool renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage);
+    virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0;
 
     // Sets the recording callback to the provided function and the recording mode
     // to CallbackAPI
@@ -75,19 +65,11 @@
         mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
     }
 
-    virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
-    bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
-
     void setTargetSdrHdrRatio(float ratio) override;
 
 protected:
-    sk_sp<SkSurface> getBufferSkSurface(
-            const renderthread::HardwareBufferRenderParams& bufferParams);
-    void dumpResourceCacheUsage() const;
-
     renderthread::RenderThread& mRenderThread;
 
-    AHardwareBuffer* mHardwareBuffer = nullptr;
     sk_sp<SkSurface> mBufferSurface = nullptr;
     sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
 
@@ -125,8 +107,6 @@
     // Set up a multi frame capture.
     bool setupMultiFrameCapture();
 
-    std::vector<sk_sp<SkImage>> mPinnedImages;
-
     // Block of properties used only for debugging to record a SkPicture and save it in a file.
     // There are three possible ways of recording drawing commands.
     enum class CaptureMode {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index fd0a8e0..d06dba0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "SkiaVulkanPipeline.h"
+#include "pipeline/skia/SkiaVulkanPipeline.h"
 
 #include <GrDirectContext.h>
 #include <GrTypes.h>
@@ -28,10 +28,10 @@
 #include "DeferredLayerUpdater.h"
 #include "LightingInfo.h"
 #include "Readback.h"
-#include "ShaderCache.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
-#include "VkInteropFunctorDrawable.h"
+#include "pipeline/skia/ShaderCache.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
+#include "pipeline/skia/VkInteropFunctorDrawable.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/Frame.h"
 #include "renderthread/IRenderPipeline.h"
@@ -42,7 +42,8 @@
 namespace uirenderer {
 namespace skiapipeline {
 
-SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {
+SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
+        : SkiaGpuPipeline(thread) {
     thread.renderState().registerContextCallback(this);
 }
 
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 0000000..9159eae
--- /dev/null
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+    SkiaGpuPipeline(renderthread::RenderThread& thread);
+    virtual ~SkiaGpuPipeline();
+
+    virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+
+    // If the given node didn't have a layer surface, or had one of the wrong size, this method
+    // creates a new one and returns true. Otherwise does nothing and returns false.
+    bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+                             ErrorHandler* errorHandler) override;
+
+    bool pinImages(std::vector<SkImage*>& mutableImages) override;
+    bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+    void unpinImages() override;
+    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+    void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override;
+    bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
+    static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
+
+protected:
+    sk_sp<SkSurface> getBufferSkSurface(
+            const renderthread::HardwareBufferRenderParams& bufferParams);
+    void dumpResourceCacheUsage() const;
+
+    AHardwareBuffer* mHardwareBuffer = nullptr;
+
+private:
+    std::vector<sk_sp<SkImage>> mPinnedImages;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
similarity index 95%
rename from libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
rename to libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
index ebe8b6e..6e74782 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
@@ -19,7 +19,7 @@
 #include <EGL/egl.h>
 #include <system/window.h>
 
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/HardwareBufferRenderParams.h"
 
@@ -30,7 +30,7 @@
 namespace uirenderer {
 namespace skiapipeline {
 
-class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
 public:
     SkiaOpenGLPipeline(renderthread::RenderThread& thread);
     virtual ~SkiaOpenGLPipeline();
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
similarity index 95%
rename from libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
rename to libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
index 624eaa5..0d30df4 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
@@ -17,7 +17,7 @@
 #pragma once
 
 #include "SkRefCnt.h"
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/HardwareBufferRenderParams.h"
 #include "renderthread/VulkanManager.h"
@@ -30,7 +30,7 @@
 namespace uirenderer {
 namespace skiapipeline {
 
-class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
 public:
     explicit SkiaVulkanPipeline(renderthread::RenderThread& thread);
     virtual ~SkiaVulkanPipeline();
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
new file mode 120000
index 0000000..4fb4784
--- /dev/null
+++ b/libs/hwui/platform/host/android/api-level.h
@@ -0,0 +1 @@
+../../../../../../../bionic/libc/include/android/api-level.h
\ No newline at end of file
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 0000000..a717265
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+#include "renderthread/Frame.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+    SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+    ~SkiaGpuPipeline() {}
+
+    bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+    bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+    void unpinImages() override {}
+
+    // If the given node didn't have a layer surface, or had one of the wrong size, this method
+    // creates a new one and returns true. Otherwise does nothing and returns false.
+    bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+                             ErrorHandler* errorHandler) override {
+        return false;
+    }
+    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {}
+    void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+    bool hasHardwareBuffer() override { return false; }
+
+    renderthread::MakeCurrentResult makeCurrent() override {
+        return renderthread::MakeCurrentResult::Failed;
+    }
+    renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); }
+    renderthread::IRenderPipeline::DrawResult draw(
+            const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+            const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+            const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+            const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override {
+        return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)};
+    }
+    bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                     bool* requireSwap) override {
+        return false;
+    }
+    DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+    bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override {
+        return false;
+    }
+    [[nodiscard]] android::base::unique_fd flush() override {
+        return android::base::unique_fd(-1);
+    };
+    void onStop() override {}
+    bool isSurfaceReady() override { return false; }
+    bool isContextReady() override { return false; }
+
+    const SkM44& getPixelSnapMatrix() const override {
+        static const SkM44 sSnapMatrix = SkM44();
+        return sSnapMatrix;
+    }
+    static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
new file mode 100644
index 0000000..4fafbcc
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaOpenGLPipeline : public SkiaGpuPipeline {
+public:
+    SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+    static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
new file mode 100644
index 0000000..d54caef
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaVulkanPipeline : public SkiaGpuPipeline {
+public:
+    SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+    static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1fbd580..22de2f2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,8 +35,8 @@
 #include "Properties.h"
 #include "RenderThread.h"
 #include "hwui/Canvas.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include "pipeline/skia/SkiaPipeline.h"
 #include "pipeline/skia/SkiaVulkanPipeline.h"
 #include "thread/CommonPool.h"
 #include "utils/GLUtils.h"
@@ -108,7 +108,7 @@
 }
 
 void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
-    skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
+    skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap);
 }
 
 CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 2904dfe..708b011 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -442,14 +442,17 @@
         }
 
         // TODO: maybe we want to get rid of the WCG check if overlay properties just works?
-        const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
-                                DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+        bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+                DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
 
-        if (canUseFp16) {
-            if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
-                colorMode = ColorMode::Default;
-            } else {
-                config = mEglConfigF16;
+        if (colorMode == ColorMode::Hdr) {
+            if (canUseFp16 && !DeviceInfo::get()->isSupportRgba10101010ForHdr()) {
+                if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
+                    // If the driver doesn't support fp16 then fallback to 8-bit
+                    canUseFp16 = false;
+                } else {
+                    config = mEglConfigF16;
+                }
             }
         }
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index b8c3a4d..ee1d1f8 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -30,8 +30,6 @@
 #include "SwapBehavior.h"
 #include "hwui/Bitmap.h"
 
-class GrDirectContext;
-
 struct ANativeWindow;
 
 namespace android {
@@ -94,7 +92,6 @@
     virtual void setSurfaceColorProperties(ColorMode colorMode) = 0;
     virtual SkColorType getSurfaceColorType() const = 0;
     virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
-    virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
     virtual void setPictureCapturedCallback(
             const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
 
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index b8b03b6..19e59a7 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -1,5 +1,4 @@
 package: "android.location.flags"
-container: "system"
 
 flag {
     name: "new_geocoder"
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3f9440b..5aa006b 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1309,18 +1309,24 @@
             return;
         }
 
-        RoutingController newController;
-        if (sessionInfo.isSystemSession()) {
-            newController = getSystemController();
-            newController.setRoutingSessionInfo(sessionInfo);
+        RoutingController newController = addRoutingController(sessionInfo);
+        notifyTransfer(oldController, newController);
+    }
+
+    @NonNull
+    private RoutingController addRoutingController(@NonNull RoutingSessionInfo session) {
+        RoutingController controller;
+        if (session.isSystemSession()) {
+            // mSystemController is never released, so we only need to update its status.
+            mSystemController.setRoutingSessionInfo(session);
+            controller = mSystemController;
         } else {
-            newController = new RoutingController(sessionInfo);
+            controller = new RoutingController(session);
             synchronized (mLock) {
-                mNonSystemRoutingControllers.put(newController.getId(), newController);
+                mNonSystemRoutingControllers.put(controller.getId(), controller);
             }
         }
-
-        notifyTransfer(oldController, newController);
+        return controller;
     }
 
     void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1329,40 +1335,12 @@
             return;
         }
 
-        if (sessionInfo.isSystemSession()) {
-            // The session info is sent from SystemMediaRoute2Provider.
-            RoutingController systemController = getSystemController();
-            systemController.setRoutingSessionInfo(sessionInfo);
-            notifyControllerUpdated(systemController);
-            return;
+        RoutingController controller =
+                getMatchingController(sessionInfo, /* logPrefix */ "updateControllerOnHandler");
+        if (controller != null) {
+            controller.setRoutingSessionInfo(sessionInfo);
+            notifyControllerUpdated(controller);
         }
-
-        RoutingController matchingController;
-        synchronized (mLock) {
-            matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
-        }
-
-        if (matchingController == null) {
-            Log.w(
-                    TAG,
-                    "updateControllerOnHandler: Matching controller not found. uniqueSessionId="
-                            + sessionInfo.getId());
-            return;
-        }
-
-        RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
-        if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
-            Log.w(
-                    TAG,
-                    "updateControllerOnHandler: Provider IDs are not matched. old="
-                            + oldInfo.getProviderId()
-                            + ", new="
-                            + sessionInfo.getProviderId());
-            return;
-        }
-
-        matchingController.setRoutingSessionInfo(sessionInfo);
-        notifyControllerUpdated(matchingController);
     }
 
     void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1371,34 +1349,47 @@
             return;
         }
 
-        RoutingController matchingController;
-        synchronized (mLock) {
-            matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
-        }
+        RoutingController matchingController =
+                getMatchingController(sessionInfo, /* logPrefix */ "releaseControllerOnHandler");
 
-        if (matchingController == null) {
-            if (DEBUG) {
-                Log.d(
-                        TAG,
-                        "releaseControllerOnHandler: Matching controller not found. "
-                                + "uniqueSessionId="
-                                + sessionInfo.getId());
+        if (matchingController != null) {
+            matchingController.releaseInternal(/* shouldReleaseSession= */ false);
+        }
+    }
+
+    @Nullable
+    private RoutingController getMatchingController(
+            RoutingSessionInfo sessionInfo, String logPrefix) {
+        if (sessionInfo.isSystemSession()) {
+            return getSystemController();
+        } else {
+            RoutingController controller;
+            synchronized (mLock) {
+                controller = mNonSystemRoutingControllers.get(sessionInfo.getId());
             }
-            return;
-        }
 
-        RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
-        if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
-            Log.w(
-                    TAG,
-                    "releaseControllerOnHandler: Provider IDs are not matched. old="
-                            + oldInfo.getProviderId()
-                            + ", new="
-                            + sessionInfo.getProviderId());
-            return;
-        }
+            if (controller == null) {
+                Log.w(
+                        TAG,
+                        logPrefix
+                                + ": Matching controller not found. uniqueSessionId="
+                                + sessionInfo.getId());
+                return null;
+            }
 
-        matchingController.releaseInternal(/* shouldReleaseSession= */ false);
+            RoutingSessionInfo oldInfo = controller.getRoutingSessionInfo();
+            if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
+                Log.w(
+                        TAG,
+                        logPrefix
+                                + ": Provider IDs are not matched. old="
+                                + oldInfo.getProviderId()
+                                + ", new="
+                                + sessionInfo.getProviderId());
+                return null;
+            }
+            return controller;
+        }
     }
 
     void onRequestCreateControllerByManagerOnHandler(
@@ -3129,20 +3120,8 @@
 
         private void onTransferred(
                 @NonNull RoutingSessionInfo oldSession, @NonNull RoutingSessionInfo newSession) {
-            if (!oldSession.isSystemSession()
-                    && !TextUtils.equals(
-                            getClientPackageName(), oldSession.getClientPackageName())) {
-                return;
-            }
-
-            if (!newSession.isSystemSession()
-                    && !TextUtils.equals(
-                            getClientPackageName(), newSession.getClientPackageName())) {
-                return;
-            }
-
-            // For successful in-session transfer, onControllerUpdated() handles it.
-            if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+            if (!isSessionRelatedToTargetPackageName(oldSession)
+                    || !isSessionRelatedToTargetPackageName(newSession)) {
                 return;
             }
 
@@ -3169,16 +3148,14 @@
 
         private void onTransferFailed(
                 @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
-            if (!session.isSystemSession()
-                    && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
+            if (!isSessionRelatedToTargetPackageName(session)) {
                 return;
             }
             notifyTransferFailure(route);
         }
 
         private void onSessionUpdated(@NonNull RoutingSessionInfo session) {
-            if (!session.isSystemSession()
-                    && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
+            if (!isSessionRelatedToTargetPackageName(session)) {
                 return;
             }
 
@@ -3193,6 +3170,15 @@
             notifyControllerUpdated(controller);
         }
 
+        /**
+         * Returns {@code true} if the session is a system session or if its client package name
+         * matches the proxy router's target package name.
+         */
+        private boolean isSessionRelatedToTargetPackageName(@NonNull RoutingSessionInfo session) {
+            return session.isSystemSession()
+                    || TextUtils.equals(getClientPackageName(), session.getClientPackageName());
+        }
+
         private void onSessionCreatedOnHandler(
                 int requestId, @NonNull RoutingSessionInfo sessionInfo) {
             MediaRouter2Manager.TransferRequest matchingRequest = null;
@@ -3237,19 +3223,19 @@
             }
         }
 
-        private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo sessionInfo) {
+        private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo updatedSession) {
             for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
                 String sessionId = request.mOldSessionInfo.getId();
-                if (!TextUtils.equals(sessionId, sessionInfo.getId())) {
+                if (!TextUtils.equals(sessionId, updatedSession.getId())) {
                     continue;
                 }
-                if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
+
+                if (updatedSession.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
                     mTransferRequests.remove(request);
-                    this.onTransferred(request.mOldSessionInfo, sessionInfo);
                     break;
                 }
             }
-            this.onSessionUpdated(sessionInfo);
+            this.onSessionUpdated(updatedSession);
         }
 
         private void onSessionReleasedOnHandler(@NonNull RoutingSessionInfo session) {
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec96..5bf1b4e 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.media.editing.flags"
-container: "system"
 
 flag {
   name: "add_media_metrics_editing"
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 91c4f11..8d6982e 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.media.flags"
-container: "system"
 
 flag {
     name: "enable_rlp_callbacks_in_media_router2"
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index 9a9a073..b165809 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.media.projection.flags"
-container: "system"
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes
 
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 97971e1..1731e5e 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -1,5 +1,4 @@
 package: "android.media.tv.flags"
-container: "system"
 
 flag {
     name: "broadcast_visibility_types"
diff --git a/media/jni/playback_flags.aconfig b/media/jni/playback_flags.aconfig
index 9d927ec..2bb0ec5 100644
--- a/media/jni/playback_flags.aconfig
+++ b/media/jni/playback_flags.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.media.playback.flags"
-container: "system"
 
 flag {
   name: "mediametadataretriever_default_rgba8888"
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 8227bdb..882afca 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -41,16 +41,15 @@
 
 using namespace std::chrono_literals;
 
-using HalSessionHint = aidl::android::hardware::power::SessionHint;
-using HalSessionMode = aidl::android::hardware::power::SessionMode;
-using HalWorkDuration = aidl::android::hardware::power::WorkDuration;
+// Namespace for AIDL types coming from the PowerHAL
+namespace hal = aidl::android::hardware::power;
 
 using android::base::StringPrintf;
 
 struct APerformanceHintSession;
 
 constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
-struct AWorkDuration : public HalWorkDuration {};
+struct AWorkDuration : public hal::WorkDuration {};
 
 struct APerformanceHintManager {
 public:
@@ -115,7 +114,7 @@
     // Last hint reported from sendHint indexed by hint value
     std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
-    std::vector<HalWorkDuration> mActualWorkDurations;
+    std::vector<hal::WorkDuration> mActualWorkDurations;
     std::string mSessionName;
     static int32_t sIDCounter;
     // The most recent set of thread IDs
@@ -207,8 +206,9 @@
         mTargetDurationNanos(targetDurationNanos),
         mFirstTargetMetTimestamp(0),
         mLastTargetMetTimestamp(0) {
-    const std::vector<HalSessionHint> sessionHintRange{ndk::enum_range<HalSessionHint>().begin(),
-                                                       ndk::enum_range<HalSessionHint>().end()};
+    const std::vector<hal::SessionHint> sessionHintRange{ndk::enum_range<hal::SessionHint>()
+                                                                 .begin(),
+                                                         ndk::enum_range<hal::SessionHint>().end()};
 
     mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
     mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
@@ -246,10 +246,10 @@
 }
 
 int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
-    HalWorkDuration workDuration{.durationNanos = actualDurationNanos,
-                                 .workPeriodStartTimestampNanos = 0,
-                                 .cpuDurationNanos = actualDurationNanos,
-                                 .gpuDurationNanos = 0};
+    hal::WorkDuration workDuration{.durationNanos = actualDurationNanos,
+                                   .workPeriodStartTimestampNanos = 0,
+                                   .cpuDurationNanos = actualDurationNanos,
+                                   .gpuDurationNanos = 0};
 
     return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
 }
@@ -323,7 +323,8 @@
 
 int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
     ndk::ScopedAStatus ret =
-            mHintSession->setMode(static_cast<int32_t>(HalSessionMode::POWER_EFFICIENCY), enabled);
+            mHintSession->setMode(static_cast<int32_t>(hal::SessionMode::POWER_EFFICIENCY),
+                                  enabled);
 
     if (!ret.isOk()) {
         ALOGE("%s: HintSession setPreferPowerEfficiency failed: %s", __FUNCTION__,
diff --git a/nfc/TEST_MAPPING b/nfc/TEST_MAPPING
index 5b5ea37..49c778d 100644
--- a/nfc/TEST_MAPPING
+++ b/nfc/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "name": "CtsNfcTestCases"
+    },
+    {
+      "name": "CtsNdefTestCases"
     }
   ]
 }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 73b29db..778f07c 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.nfc"
-container: "system"
 
 flag {
     name: "enable_nfc_mainline"
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 35d7393..8627eac 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.crashrecovery.flags"
-container: "system"
 
 flag {
     name: "recoverability_detection"
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 6f20adf..75a8bdf 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 271d552..f86eb61 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -31,7 +31,6 @@
 import android.crashrecovery.flags.Flags;
 import android.os.Build;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -44,10 +43,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 0000000..fa4d6af
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+    private ArrayUtils() { /* cannot be instantiated */ }
+    public static final File[] EMPTY_FILE = new File[0];
+
+
+    /**
+     * Return first index of {@code value} in {@code array}, or {@code -1} if
+     * not found.
+     */
+    public static <T> int indexOf(@Nullable T[] array, T value) {
+        if (array == null) return -1;
+        for (int i = 0; i < array.length; i++) {
+            if (Objects.equals(array[i], value)) return i;
+        }
+        return -1;
+    }
+
+    /** @hide */
+    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+        return (val != null) ? val : EMPTY_FILE;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * True if the byte array is null or has length 0.
+     */
+    public static boolean isEmpty(@Nullable byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * Converts from List of bytes to byte array
+     * @param list
+     * @return byte[]
+     */
+    public static byte[] toPrimitive(List<byte[]> list) {
+        if (list.size() == 0) {
+            return new byte[0];
+        }
+        int byteLen = list.get(0).length;
+        byte[] array = new byte[list.size() * byteLen];
+        for (int i = 0; i < list.size(); i++) {
+            for (int j = 0; j < list.get(i).length; j++) {
+                array[i * byteLen + j] = list.get(i)[j];
+            }
+        }
+        return array;
+    }
+
+    /**
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+        return appendInt(cur, val, false);
+    }
+
+    /**
+     * Adds value to given array.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+            boolean allowDuplicates) {
+        if (cur == null) {
+            return new int[] { val };
+        }
+        final int n = cur.length;
+        if (!allowDuplicates) {
+            for (int i = 0; i < n; i++) {
+                if (cur[i] == val) {
+                    return cur;
+                }
+            }
+        }
+        int[] ret = new int[n + 1];
+        System.arraycopy(cur, 0, ret, 0, n);
+        ret[n] = val;
+        return ret;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
new file mode 100644
index 0000000..afcf689
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -0,0 +1,103 @@
+/*
+ *  * Copyright (C) 2024 The Android Open 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.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Thread for asynchronous event processing. This thread is configured as
+ * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
+ * resources will be dedicated to it, and it will "have less chance of impacting
+ * the responsiveness of the user interface."
+ * <p>
+ * This thread is best suited for tasks that the user is not actively waiting
+ * for, or for tasks that the user expects to be executed eventually.
+ *
+ * @see com.android.internal.os.BackgroundThread
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public final class BackgroundThread extends HandlerThread {
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static BackgroundThread sInstance;
+    @GuardedBy("sLock")
+    private static Handler sHandler;
+    @GuardedBy("sLock")
+    private static HandlerExecutor sHandlerExecutor;
+
+    private BackgroundThread() {
+        super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    }
+
+    @GuardedBy("sLock")
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new BackgroundThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandlerExecutor = new HandlerExecutor(sHandler);
+        }
+    }
+
+    /**
+     * Get the singleton instance of this class.
+     *
+     * @return the singleton instance of this class
+     */
+    @NonNull
+    public static BackgroundThread get() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    /**
+     * Get the singleton {@link Handler} for this class.
+     *
+     * @return the singleton {@link Handler} for this class.
+     */
+    @NonNull
+    public static Handler getHandler() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+
+    /**
+     * Get the singleton {@link Executor} for this class.
+     *
+     * @return the singleton {@link Executor} for this class.
+     */
+    @NonNull
+    public static Executor getExecutor() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sHandlerExecutor;
+        }
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..e4923bf
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+    /**
+     * Read a text file into a String, optionally limiting the length.
+     *
+     * @param file     to read (will not seek, so things like /proc files are OK)
+     * @param max      length (positive for head, negative of tail, 0 for no limit)
+     * @param ellipsis to add of the file was truncated (can be null)
+     * @return the contents of the file, possibly truncated
+     * @throws IOException if something goes wrong reading the file
+     * @hide
+     */
+    public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+            @Nullable String ellipsis) throws IOException {
+        InputStream input = new FileInputStream(file);
+        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+        // input stream, bytes read not equal to buffer size is not necessarily the correct
+        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+        BufferedInputStream bis = new BufferedInputStream(input);
+        try {
+            long size = file.length();
+            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
+                if (size > 0 && (max == 0 || size < max)) max = (int) size;
+                byte[] data = new byte[max + 1];
+                int length = bis.read(data);
+                if (length <= 0) return "";
+                if (length <= max) return new String(data, 0, length);
+                if (ellipsis == null) return new String(data, 0, max);
+                return new String(data, 0, max) + ellipsis;
+            } else if (max < 0) {  // "tail" mode: keep the last N
+                int len;
+                boolean rolled = false;
+                byte[] last = null;
+                byte[] data = null;
+                do {
+                    if (last != null) rolled = true;
+                    byte[] tmp = last;
+                    last = data;
+                    data = tmp;
+                    if (data == null) data = new byte[-max];
+                    len = bis.read(data);
+                } while (len == data.length);
+
+                if (last == null && len <= 0) return "";
+                if (last == null) return new String(data, 0, len);
+                if (len > 0) {
+                    rolled = true;
+                    System.arraycopy(last, len, last, 0, last.length - len);
+                    System.arraycopy(data, 0, last, last.length - len, len);
+                }
+                if (ellipsis == null || !rolled) return new String(last);
+                return ellipsis + new String(last);
+            } else {  // "cat" mode: size unknown, read it all in streaming fashion
+                ByteArrayOutputStream contents = new ByteArrayOutputStream();
+                int len;
+                byte[] data = new byte[1024];
+                do {
+                    len = bis.read(data);
+                    if (len > 0) contents.write(data, 0, len);
+                } while (len == data.length);
+                return contents.toString();
+            }
+        } finally {
+            bis.close();
+            input.close();
+        }
+    }
+
+    /**
+     * Perform an fsync on the given FileOutputStream. The stream at this
+     * point must be flushed but not yet closed.
+     *
+     * @hide
+     */
+    public static boolean sync(FileOutputStream stream) {
+        try {
+            if (stream != null) {
+                stream.getFD().sync();
+            }
+            return true;
+        } catch (IOException e) {
+        }
+        return false;
+    }
+
+    /**
+     * List the files in the directory or return empty file.
+     *
+     * @hide
+     */
+    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+            : ArrayUtils.EMPTY_FILE;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
new file mode 100644
index 0000000..fdb15e2
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+    private final Handler mHandler;
+
+    public HandlerExecutor(@NonNull Handler handler) {
+        mHandler = Objects.requireNonNull(handler);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 0000000..5cdc253
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+    private long[] mValues;
+    private int mSize;
+    private int mHead;
+    private int mTail;
+
+    private long[] newUnpaddedLongArray(int num) {
+        return new long[num];
+    }
+    /**
+     * Initializes a queue with the given starting capacity.
+     *
+     * @param initialCapacity the capacity.
+     */
+    public LongArrayQueue(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Initializes a queue with default starting capacity.
+     */
+    public LongArrayQueue() {
+        this(16);
+    }
+
+    /** @hide */
+    public static int growSize(int currentSize) {
+        return currentSize <= 4 ? 8 : currentSize * 2;
+    }
+
+    private void grow() {
+        if (mSize < mValues.length) {
+            throw new IllegalStateException("Queue not full yet!");
+        }
+        final int newSize = growSize(mSize);
+        final long[] newArray = newUnpaddedLongArray(newSize);
+        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+        System.arraycopy(mValues, mHead, newArray, 0, r);
+        System.arraycopy(mValues, 0, newArray, r, mHead);
+        mValues = newArray;
+        mHead = 0;
+        mTail = mSize;
+    }
+
+    /**
+     * Returns the number of elements in the queue.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Removes all elements from this queue.
+     */
+    public void clear() {
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Adds a value to the tail of the queue.
+     *
+     * @param value the value to be added.
+     */
+    public void addLast(long value) {
+        if (mSize == mValues.length) {
+            grow();
+        }
+        mValues[mTail] = value;
+        mTail = (mTail + 1) % mValues.length;
+        mSize++;
+    }
+
+    /**
+     * Removes an element from the head of the queue.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long removeFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final long ret = mValues[mHead];
+        mHead = (mHead + 1) % mValues.length;
+        mSize--;
+        return ret;
+    }
+
+    /**
+     * Returns the element at the given position from the head of the queue, where 0 represents the
+     * head of the queue.
+     *
+     * @param position the position from the head of the queue.
+     * @return the element found at the given position.
+     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+     *                                   {@code position} >= {@link #size()}
+     */
+    public long get(int position) {
+        if (position < 0 || position >= mSize) {
+            throw new IndexOutOfBoundsException("Index " + position
+                + " not valid for a queue of size " + mSize);
+        }
+        final int index = (mHead + position) % mValues.length;
+        return mValues[index];
+    }
+
+    /**
+     * Returns the element at the head of the queue, without removing it.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty
+     */
+    public long peekFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        return mValues[mHead];
+    }
+
+    /**
+     * Returns the element at the tail of the queue.
+     *
+     * @return the element at the tail of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long peekLast() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+        return mValues[index];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        if (mSize <= 0) {
+            return "{}";
+        }
+
+        final StringBuilder buffer = new StringBuilder(mSize * 64);
+        buffer.append('{');
+        buffer.append(get(0));
+        for (int i = 1; i < mSize; i++) {
+            buffer.append(", ");
+            buffer.append(get(i));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..dbbef61
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+    private static final String STRING_ARRAY_SEPARATOR = ":";
+
+    /** @hide */
+    public static final void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type = parser.next()) != parser.START_TAG
+            && type != parser.END_DOCUMENT) {
+            // Do nothing
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+                + ", expected " + firstElementName);
+        }
+    }
+
+    /** @hide */
+    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+            throws IOException, XmlPullParserException {
+        for (;;) {
+            int type = parser.next();
+            if (type == XmlPullParser.END_DOCUMENT
+                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+                return false;
+            }
+            if (type == XmlPullParser.START_TAG
+                    && parser.getDepth() == outerDepth + 1) {
+                return true;
+            }
+        }
+    }
+
+    private static XmlPullParser newPullParser() {
+        try {
+            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /** @hide */
+    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+            throws IOException {
+        final byte[] magic = new byte[4];
+        if (in instanceof FileInputStream) {
+            try {
+                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        } else {
+            if (!in.markSupported()) {
+                in = new BufferedInputStream(in);
+            }
+            in.mark(8);
+            in.read(magic);
+            in.reset();
+        }
+
+        final TypedXmlPullParser xml;
+        xml = (TypedXmlPullParser) newPullParser();
+        try {
+            xml.setInput(in, "UTF_8");
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        return xml;
+    }
+}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index bc35a85..9fd386f 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,6 +68,13 @@
   <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
   <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
+  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
+  <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
+  <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
+  <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
+  <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
   <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
   <string name="passkey">passkey</string>
   <string name="password">password</string>
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index f2c252e..b408c15 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -51,6 +51,8 @@
 import com.android.credentialmanager.model.BiometricRequestInfo
 import com.android.credentialmanager.model.EntryInfo
 
+const val CREDENTIAL_ENTRY_PREFIX = "androidx.credentials.provider.credentialEntry."
+
 fun EntryInfo.getIntentSenderRequest(
     isAutoSelected: Boolean = false
 ): IntentSenderRequest? {
@@ -140,7 +142,8 @@
                     isDefaultIconPreferredAsSingleProvider =
                             credentialEntry.isDefaultIconPreferredAsSingleProvider,
                     affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
-                    biometricRequest = predetermineAndValidateBiometricFlow(it),
+                    biometricRequest = predetermineAndValidateBiometricFlow(it,
+                        CREDENTIAL_ENTRY_PREFIX),
                 )
                 )
             }
@@ -169,7 +172,8 @@
                     isDefaultIconPreferredAsSingleProvider =
                             credentialEntry.isDefaultIconPreferredAsSingleProvider,
                     affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
-                    biometricRequest = predetermineAndValidateBiometricFlow(it),
+                    biometricRequest = predetermineAndValidateBiometricFlow(it,
+                        CREDENTIAL_ENTRY_PREFIX),
                 )
                 )
             }
@@ -197,7 +201,8 @@
                     isDefaultIconPreferredAsSingleProvider =
                             credentialEntry.isDefaultIconPreferredAsSingleProvider,
                     affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
-                    biometricRequest = predetermineAndValidateBiometricFlow(it),
+                    biometricRequest = predetermineAndValidateBiometricFlow(it,
+                        CREDENTIAL_ENTRY_PREFIX),
                 )
                 )
             }
@@ -217,21 +222,26 @@
  * Note that the required values, such as the provider info's icon or display name, or the entries
  * credential type or userName, and finally the display info's app name, are non-null and must
  * exist to run through the flow.
+ *
+ * @param hintPrefix a string prefix indicating the type of entry being utilized, since both create
+ * and get flows utilize slice params; includes the final '.' before the name of the type (e.g.
+ * androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have
+ * 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.")
  * // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
  * // expected to be used. When it is added, it should be lightly validated.
  */
-private fun predetermineAndValidateBiometricFlow(
-    it: Entry
+fun predetermineAndValidateBiometricFlow(
+    entry: Entry,
+    hintPrefix: String,
 ): BiometricRequestInfo? {
     // TODO(b/326243754) : When available, use the official jetpack structured type
-    val allowedAuthenticators: Int? = it.slice.items.firstOrNull {
-        it.hasHint("androidx.credentials." +
-                "provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS")
+    val allowedAuthenticators: Int? = entry.slice.items.firstOrNull {
+        it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS")
     }?.int
 
     // This is optional and does not affect validating the biometric flow in any case
-    val opId: Int? = it.slice.items.firstOrNull {
-        it.hasHint("androidx.credentials.provider.credentialEntry.SLICE_HINT_CRYPTO_OP_ID")
+    val opId: Int? = entry.slice.items.firstOrNull {
+        it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID")
     }?.int
     if (allowedAuthenticators != null) {
         return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 28c4047..a039753 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.app.Activity
 import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResult
 import android.os.IBinder
 import android.text.TextUtils
 import android.util.Log
@@ -39,6 +40,7 @@
 import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.findBiometricFlowEntry
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
 import com.android.credentialmanager.logging.LifecycleEvent
@@ -304,7 +306,11 @@
         uiState = uiState.copy(
             createCredentialUiState = uiState.createCredentialUiState?.copy(
                 currentScreenState =
-                if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
+                // An autoselect flow never makes it to the more options screen
+                if (findBiometricFlowEntry(activeEntry = activeEntry,
+                        isAutoSelectFlow = false) != null) CreateScreenState.BIOMETRIC_SELECTION
+                else if (
+                    uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
                         ?.contains(activeEntry.activeProvider.id) ?: true ||
                     !(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
                     ?: false) ||
@@ -330,7 +336,10 @@
         )
     }
 
-    fun createFlowOnEntrySelected(selectedEntry: EntryInfo) {
+    fun createFlowOnEntrySelected(
+        selectedEntry: EntryInfo,
+        authResult: AuthenticationResult? = null
+    ) {
         val providerId = selectedEntry.providerId
         val entryKey = selectedEntry.entryKey
         val entrySubkey = selectedEntry.entrySubkey
@@ -341,6 +350,9 @@
             uiState = uiState.copy(
                 selectedEntry = selectedEntry,
                 providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
+                biometricState = if (authResult == null) uiState.biometricState else uiState
+                    .biometricState.copy(biometricResult = BiometricResult(
+                        biometricAuthenticationResult = authResult))
             )
         } else {
             credManRepo.onOptionSelected(
@@ -367,9 +379,4 @@
     fun logUiEvent(uiEventEnum: UiEventEnum) {
         this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
     }
-
-    companion object {
-        // TODO(b/326243754) : Replace/remove once all failure flows added in
-        const val TEMPORARY_FAILURE_CODE = Integer.MIN_VALUE
-    }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index fd6fc6a..358ebfa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -52,10 +52,11 @@
 import androidx.credentials.provider.RemoteEntry
 import org.json.JSONObject
 import android.credentials.flags.Flags
+import com.android.credentialmanager.createflow.isBiometricFlow
 import com.android.credentialmanager.getflow.TopBrandingContent
+import com.android.credentialmanager.ktx.predetermineAndValidateBiometricFlow
 import java.time.Instant
 
-
 fun getAppLabel(
     pm: PackageManager,
     appPackageName: String
@@ -237,6 +238,9 @@
 
 class CreateFlowUtils {
     companion object {
+
+        private const val CREATE_ENTRY_PREFIX = "androidx.credentials.provider.createEntry."
+
         /**
          * Note: caller required handle empty list due to parsing error.
          */
@@ -417,12 +421,21 @@
                 }
             }
             val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
+            val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
+                compareByDescending { it.first.lastUsedTime }
+            )
+            val activeEntry = toActiveEntry(
+                defaultProvider = defaultProvider,
+                sortedCreateOptionsPairs = sortedCreateOptionsPairs,
+                remoteEntry = remoteEntry,
+                remoteEntryProvider = remoteEntryProvider,
+            )
+            val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry,
+                sortedCreateOptionsPairs, requestDisplayInfo)
             val initialScreenState = toCreateScreenState(
                 createOptionSize = createOptionsPairs.size,
                 remoteEntry = remoteEntry,
-            )
-            val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
-                compareByDescending { it.first.lastUsedTime }
+                isBiometricFlow = isBiometricFlow
             )
             return CreateCredentialUiState(
                 enabledProviders = enabledProviders,
@@ -430,12 +443,7 @@
                 currentScreenState = initialScreenState,
                 requestDisplayInfo = requestDisplayInfo,
                 sortedCreateOptionsPairs = sortedCreateOptionsPairs,
-                activeEntry = toActiveEntry(
-                    defaultProvider = defaultProvider,
-                    sortedCreateOptionsPairs = sortedCreateOptionsPairs,
-                    remoteEntry = remoteEntry,
-                    remoteEntryProvider = remoteEntryProvider,
-                ),
+                activeEntry = activeEntry,
                 remoteEntry = remoteEntry,
                 foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
             )
@@ -444,9 +452,12 @@
         fun toCreateScreenState(
             createOptionSize: Int,
             remoteEntry: RemoteInfo?,
+            isBiometricFlow: Boolean,
         ): CreateScreenState {
             return if (createOptionSize == 0 && remoteEntry != null) {
                 CreateScreenState.EXTERNAL_ONLY_SELECTION
+            } else if (isBiometricFlow) {
+                CreateScreenState.BIOMETRIC_SELECTION
             } else {
                 CreateScreenState.CREATION_OPTION_SELECTION
             }
@@ -503,8 +514,8 @@
                         it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
                             "SELECT_ALLOWED")
                     }?.text == "true",
-                    // TODO(b/326243754) : Handle this when the create flow is added; for now the
-                    // create flow does not support biometric values
+                    biometricRequest = predetermineAndValidateBiometricFlow(it,
+                        CREATE_ENTRY_PREFIX),
                 )
                 )
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index a30956e..fa17735 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -26,11 +26,14 @@
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.R
 import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.getCreateTitleResCode
 import com.android.credentialmanager.getflow.ProviderDisplayInfo
 import com.android.credentialmanager.getflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
 import com.android.credentialmanager.model.BiometricRequestInfo
+import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.model.get.ProviderInfo
 import java.lang.Exception
@@ -39,14 +42,30 @@
  * Aggregates common display information used for the Biometric Flow.
  * Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
  * [providerName], which represents the name of the provider, the [displayTitleText] which is
- * the large text displaying the flow in progress, and the [descriptionAboveBiometricButton], which
+ * the large text displaying the flow in progress, and the [descriptionForCredential], which
  * describes details of where the credential is being saved, and how.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com:
+ *
+ * 'get' flow:
+ *     - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ *     - [displayTitleText] = "Use your saved passkey for Any Provider?"
+ *     - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
+ *     Your@Email.com"
+ *
+ * 'create' flow:
+ *     - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ *     - [displayTitleText] = "Create passkey to sign in to Any Provider?"
+ *     - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ * ).
+ *
+ * The above are examples; the credential type can change depending on scenario.
+ * // TODO(b/326243891) : Finalize once all the strings and create flow is iterated to completion
  */
 data class BiometricDisplayInfo(
     val providerIcon: Bitmap,
     val providerName: String,
     val displayTitleText: String,
-    val descriptionAboveBiometricButton: String,
+    val descriptionForCredential: String,
     val biometricRequestInfo: BiometricRequestInfo,
 )
 
@@ -56,10 +75,7 @@
  * additional states that may improve the flow.
  */
 data class BiometricState(
-    val biometricResult: BiometricResult? = null,
-    val biometricError: BiometricError? = null,
-    val biometricHelp: BiometricHelp? = null,
-    val biometricAcquireInfo: Int? = null,
+    val biometricResult: BiometricResult? = null
 )
 
 /**
@@ -108,18 +124,20 @@
     .RequestDisplayInfo? = null,
     createProviderInfo: EnabledProviderInfo? = null,
 ) {
+    // TODO(b/330396089) : Add rotation configuration fix with state machine
     var biometricDisplayInfo: BiometricDisplayInfo? = null
+    var flowType = FlowType.GET
     if (getRequestDisplayInfo != null) {
         biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo,
             getProviderInfoList,
             getProviderDisplayInfo,
             context, biometricEntry)
     } else if (createRequestDisplayInfo != null) {
-        // TODO(b/326243754) : Create Flow to be implemented in follow up
-        biometricDisplayInfo = validateBiometricCreateFlow(
+        flowType = FlowType.CREATE
+        biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
             createRequestDisplayInfo,
-            createProviderInfo
-        )
+            createProviderInfo,
+            context, biometricEntry)
     }
 
     if (biometricDisplayInfo == null) {
@@ -128,7 +146,7 @@
     }
 
     val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
-        biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators)
+        biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators, flowType)
 
     val callback: BiometricPrompt.AuthenticationCallback =
         setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
@@ -154,23 +172,21 @@
 /**
  * Sets up the biometric prompt with the UI specific bits.
  * // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject
- * // TODO(b/326243754) : Given fallbacks aren't allowed, for now we validate that device creds
- * // are NOT allowed to be passed in to avoid throwing an error. Later, however, once target
- * // alignments occur, we should add the bit back properly.
  */
 private fun setupBiometricPrompt(
     context: Context,
     biometricDisplayInfo: BiometricDisplayInfo,
     openMoreOptionsPage: () -> Unit,
     requestAllowedAuthenticators: Int,
+    flowType: FlowType,
 ): BiometricPrompt {
     val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators)
 
     val biometricPrompt = BiometricPrompt.Builder(context)
         .setTitle(biometricDisplayInfo.displayTitleText)
         // TODO(b/326243754) : Migrate to using new methods recently aligned upon
-        .setNegativeButton(context.getString(R.string
-                .dropdown_presentation_more_sign_in_options_text),
+        .setNegativeButton(context.getString(if (flowType == FlowType.GET) R.string
+                .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
             getMainExecutor(context)) { _, _ ->
             openMoreOptionsPage()
         }
@@ -178,7 +194,7 @@
         .setConfirmationRequired(true)
         .setLogoBitmap(biometricDisplayInfo.providerIcon)
         .setLogoDescription(biometricDisplayInfo.providerName)
-        .setDescription(biometricDisplayInfo.descriptionAboveBiometricButton)
+        .setDescription(biometricDisplayInfo.descriptionForCredential)
         .build()
 
     return biometricPrompt
@@ -294,14 +310,16 @@
  * checking between the two. The reason for this method matches the logic for the
  * [validateBiometricGetFlow] with the only difference being that this is for the create flow.
  */
-private fun validateBiometricCreateFlow(
+private fun validateAndRetrieveBiometricCreateDisplayInfo(
     createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?,
     createProviderInfo: EnabledProviderInfo?,
+    context: Context,
+    selectedEntry: EntryInfo,
 ): BiometricDisplayInfo? {
     if (createRequestDisplayInfo != null && createProviderInfo != null) {
-    } else if (createRequestDisplayInfo != null && createProviderInfo != null) {
-        // TODO(b/326243754) : Create Flow to be implemented in follow up
-        return createFlowDisplayValues()
+        if (selectedEntry !is CreateOptionInfo) { return null }
+        return createBiometricDisplayValues(createRequestDisplayInfo, createProviderInfo, context,
+            selectedEntry)
     }
     return null
 }
@@ -346,17 +364,47 @@
         username
     )
     return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
-        displayTitleText = displayTitleText, descriptionAboveBiometricButton = descriptionText,
+        displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
         biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
 }
 
 /**
- * Handles the biometric sign in via the 'create credentials' flow, or early validates this flow
- * needs to fallback.
+ * Handles the biometric sign in via the create credentials flow. Stricter in the get flow in that
+ * if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null
+ * value unlike the get counterpart.
  */
-private fun createFlowDisplayValues(): BiometricDisplayInfo? {
-    // TODO(b/326243754) : Create Flow to be implemented in follow up
-    return null
+private fun createBiometricDisplayValues(
+    createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo,
+    createProviderInfo: EnabledProviderInfo,
+    context: Context,
+    selectedEntry: CreateOptionInfo,
+): BiometricDisplayInfo {
+    val icon: Bitmap?
+    val providerName: String?
+    val displayTitleText: String?
+    icon = createProviderInfo.icon.toBitmap()
+    providerName = createProviderInfo.displayName
+    displayTitleText = context.getString(
+        getCreateTitleResCode(createRequestDisplayInfo),
+        createRequestDisplayInfo.appName
+    )
+    val descriptionText: String = context.getString(
+        when (createRequestDisplayInfo.type) {
+            CredentialType.PASSKEY ->
+                R.string.choose_create_single_tap_passkey_title
+
+            CredentialType.PASSWORD ->
+                R.string.choose_create_single_tap_password_title
+
+            CredentialType.UNKNOWN ->
+                R.string.choose_create_single_tap_sign_in_title
+        },
+        createRequestDisplayInfo.appName,
+    )
+    // TODO(b/327620327) : Add a subtitle and any other recently aligned ideas
+    return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
+        displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
+        biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
index 5d2b0ad2..f6140f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.credentialmanager.common
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+enum class FlowType {
+    GET,
+    CREATE
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index af78573..25fb477 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -17,6 +17,7 @@
 package com.android.credentialmanager.createflow
 
 import android.credentials.flags.Flags.selectorUiImprovementsEnabled
+import android.hardware.biometrics.BiometricPrompt
 import android.text.TextUtils
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
@@ -26,7 +27,6 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material3.Divider
 import androidx.compose.material.icons.Icons
@@ -38,6 +38,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
@@ -49,6 +50,7 @@
 import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.common.ProviderActivityState
 import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
+import com.android.credentialmanager.common.runBiometricFlow
 import com.android.credentialmanager.common.ui.ActionButton
 import com.android.credentialmanager.common.ui.BodyMediumText
 import com.android.credentialmanager.common.ui.BodySmallText
@@ -95,6 +97,22 @@
                                 viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
                                 onLog = { viewModel.logUiEvent(it) },
                         )
+                        CreateScreenState.BIOMETRIC_SELECTION ->
+                            BiometricSelectionPage(
+                                biometricEntry = createCredentialUiState
+                                    .activeEntry?.activeEntryInfo,
+                                onCancelFlowAndFinish = viewModel::onUserCancel,
+                                onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
+                                onMoreOptionSelected =
+                                viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+                                requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+                                enabledProviderInfo = createCredentialUiState
+                                        .activeEntry?.activeProvider!!,
+                                onBiometricEntrySelected =
+                                viewModel::createFlowOnEntrySelected,
+                                fallbackToOriginalFlow =
+                                viewModel::getFlowOnBackToPrimarySelectionScreen,
+                            )
                         CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
                                 requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
                                 enabledProviderList = createCredentialUiState.enabledProviders,
@@ -313,20 +331,9 @@
         item { Divider(thickness = 16.dp, color = Color.Transparent) }
         item {
             HeadlineText(
-                text = when (requestDisplayInfo.type) {
-                    CredentialType.PASSKEY -> stringResource(
-                        R.string.choose_create_option_passkey_title,
-                        requestDisplayInfo.appName
-                    )
-                    CredentialType.PASSWORD -> stringResource(
-                        R.string.choose_create_option_password_title,
-                        requestDisplayInfo.appName
-                    )
-                    CredentialType.UNKNOWN -> stringResource(
-                        R.string.choose_create_option_sign_in_title,
-                        requestDisplayInfo.appName
-                    )
-                }
+                text = stringResource(
+                    getCreateTitleResCode(requestDisplayInfo),
+                    requestDisplayInfo.appName)
             )
         }
         item { Divider(thickness = 24.dp, color = Color.Transparent) }
@@ -560,4 +567,32 @@
         iconImageVector = Icons.Outlined.QrCodeScanner,
         entryHeadlineText = stringResource(R.string.another_device),
     )
-}
\ No newline at end of file
+}
+
+@Composable
+internal fun BiometricSelectionPage(
+    biometricEntry: EntryInfo?,
+    onMoreOptionSelected: () -> Unit,
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderInfo: EnabledProviderInfo,
+    onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+    onCancelFlowAndFinish: () -> Unit,
+    onIllegalScreenStateAndFinish: (String) -> Unit,
+    fallbackToOriginalFlow: () -> Unit,
+) {
+    if (biometricEntry == null) {
+        fallbackToOriginalFlow()
+        return
+    }
+    runBiometricFlow(
+        biometricEntry = biometricEntry,
+        context = LocalContext.current,
+        openMoreOptionsPage = onMoreOptionSelected,
+        sendDataToProvider = onBiometricEntrySelected,
+        onCancelFlowAndFinish = onCancelFlowAndFinish,
+        createRequestDisplayInfo = requestDisplayInfo,
+        createProviderInfo = enabledProviderInfo,
+        onBiometricFailureFallback = fallbackToOriginalFlow,
+        onIllegalStateAndFinish = onIllegalScreenStateAndFinish,
+    )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 617a981..1d262ba 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -16,9 +16,11 @@
 
 package com.android.credentialmanager.createflow
 
+import android.credentials.flags.Flags.credmanBiometricApiEnabled
 import android.graphics.drawable.Drawable
-import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.R
 import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
 import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.creation.RemoteInfo
 
@@ -33,14 +35,99 @@
     val foundCandidateFromUserDefaultProvider: Boolean,
 )
 
+/**
+ * Checks if this create flow is a biometric flow. Note that this flow differs slightly from the
+ * autoselect 'get' flow. Namely, given there can be multiple providers, rather than multiple
+ * accounts, the idea is that autoselect is ever only enabled for a single provider (or even, in
+ * that case, a single 'type' (family only, or work only) for a provider). However, for all other
+ * cases, the biometric screen should always show up if that entry contains the biometric bit.
+ */
+internal fun findBiometricFlowEntry(
+    activeEntry: ActiveEntry,
+    isAutoSelectFlow: Boolean,
+): CreateOptionInfo? {
+    if (!credmanBiometricApiEnabled()) {
+        return null
+    }
+    if (isAutoSelectFlow) {
+        // Since this is the create flow, auto select will only ever be true for a single provider.
+        // However, for all other cases, biometric should be used if that bit is opted into. If
+        // they clash, autoselect is always preferred, but that's only if there's a single provider.
+        return null
+    }
+    val biometricEntry = getCreateEntry(activeEntry)
+    return if (biometricEntry?.biometricRequest != null) biometricEntry else null
+}
+
+/**
+ * Retrieves the activeEntry by validating it is a [CreateOptionInfo]. This is done by ensuring
+ * that the [activeEntry] exists as a [CreateOptionInfo] to retrieve its [EntryInfo].
+ */
+internal fun getCreateEntry(
+    activeEntry: ActiveEntry?,
+): CreateOptionInfo? {
+    val entry = activeEntry?.activeEntryInfo
+    if (entry !is CreateOptionInfo) {
+        return null
+    }
+    return entry
+}
+
+/**
+* Determines if the flow is a biometric flow by taking into account autoselect criteria.
+*/
+internal fun isBiometricFlow(
+    activeEntry: ActiveEntry,
+    sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+    requestDisplayInfo: RequestDisplayInfo,
+) = findBiometricFlowEntry(activeEntry, isFlowAutoSelectable(
+    requestDisplayInfo = requestDisplayInfo,
+    activeEntry = activeEntry,
+    sortedCreateOptionsPairs = sortedCreateOptionsPairs
+)) != null
+
+/**
+ * This utility presents the correct resource string for the create flows title conditionally.
+ * Similar to generateDisplayTitleTextResCode in the 'get' flow, but for the create flow instead.
+ * This is for the title, and is a shared resource, unlike the specific unlock request text.
+ * E.g. this will look something like: "Create passkey to sign in to Tribank."
+ * // TODO(b/330396140) : Validate approach and add dynamic auth strings
+ */
+internal fun getCreateTitleResCode(createRequestDisplayInfo: RequestDisplayInfo): Int =
+    when (createRequestDisplayInfo.type) {
+        CredentialType.PASSKEY ->
+            R.string.choose_create_option_passkey_title
+
+        CredentialType.PASSWORD ->
+            R.string.choose_create_option_password_title
+
+        CredentialType.UNKNOWN ->
+            R.string.choose_create_option_sign_in_title
+    }
+
 internal fun isFlowAutoSelectable(
     uiState: CreateCredentialUiState
 ): Boolean {
-  return uiState.requestDisplayInfo.isAutoSelectRequest &&
-      uiState.sortedCreateOptionsPairs.size == 1 &&
-      uiState.activeEntry?.activeEntryInfo?.let {
-        it is CreateOptionInfo && it.allowAutoSelect
-      } ?: false
+    return isFlowAutoSelectable(uiState.requestDisplayInfo, uiState.activeEntry,
+        uiState.sortedCreateOptionsPairs)
+}
+
+/**
+ * When initializing, the [CreateCredentialUiState] is generated after the initial screen is set.
+ * This overloaded method allows identifying if the flow is auto selectable prior to the creation
+ * of the [CreateCredentialUiState].
+ */
+internal fun isFlowAutoSelectable(
+    requestDisplayInfo: RequestDisplayInfo,
+    activeEntry: ActiveEntry?,
+    sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>
+): Boolean {
+    val isAutoSelectRequest = requestDisplayInfo.isAutoSelectRequest
+    if (sortedCreateOptionsPairs.size != 1) {
+        return false
+    }
+    val singleEntry = getCreateEntry(activeEntry)
+    return isAutoSelectRequest && singleEntry?.allowAutoSelect == true
 }
 
 internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
@@ -95,6 +182,7 @@
 /** The name of the current screen. */
 enum class CreateScreenState {
   CREATION_OPTION_SELECTION,
+  BIOMETRIC_SELECTION,
   MORE_OPTIONS_SELECTION,
   DEFAULT_PROVIDER_CONFIRMATION,
   EXTERNAL_ONLY_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 4d7272c..6d1a3dd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -144,8 +144,6 @@
                         } else if (credmanBiometricApiEnabled() && getCredentialUiState
                                 .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
                             BiometricSelectionPage(
-                                // TODO(b/326243754) : Utilize expected entry for this flow, confirm
-                                // activeEntry will always be what represents the single tap flow
                                 biometricEntry = getCredentialUiState.activeEntry,
                                 onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
                                 onCancelFlowAndFinish = viewModel::onUserCancel,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 6d5b52a..ac776af 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -238,6 +238,7 @@
 /**
  * This generates the res code for the large display title text for the selector. For example, it
  * retrieves the resource for strings like: "Use your saved passkey for *rpName*".
+ * TODO(b/330396140) : Validate approach and add dynamic auth strings
  */
 internal fun generateDisplayTitleTextResCode(
     singleEntryType: CredentialType,
diff --git a/packages/InputDevices/res/raw/keyboard_layout_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
index fbb9bb6..1fb0924 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
@@ -28,82 +28,93 @@
 
 key GRAVE {
     label:                              '^'
-    base:                               '^'
+    base:                               '\u0302'
     shift:                              '\u00b0'
 }
 
 key 1 {
     label:                              '1'
     base:                               '1'
-    shift:                              '!'
+    shift, capslock:                    '!'
+    shift+capslock:                     '1'
 }
 
 key 2 {
     label:                              '2'
     base:                               '2'
-    shift:                              '"'
+    shift, capslock:                    '"'
+    shift+capslock:                     '2'
     ralt:                               '\u00b2'
 }
 
 key 3 {
     label:                              '3'
     base:                               '3'
-    shift:                              '\u00a7'
+    shift, capslock:                    '\u00a7'
+    shift+capslock:                     '3'
     ralt:                               '\u00b3'
 }
 
 key 4 {
     label:                              '4'
     base:                               '4'
-    shift:                              '$'
+    shift, capslock:                    '$'
+    shift+capslock:                     '4'
 }
 
 key 5 {
     label:                              '5'
     base:                               '5'
-    shift:                              '%'
+    shift, capslock:                    '%'
+    shift+capslock:                     '5'
 }
 
 key 6 {
     label:                              '6'
     base:                               '6'
-    shift:                              '&'
+    shift, capslock:                    '&'
+    shift+capslock:                     '6'
 }
 
 key 7 {
     label:                              '7'
     base:                               '7'
-    shift:                              '/'
+    shift, capslock:                    '/'
+    shift+capslock:                     '7'
     ralt:                               '{'
 }
 
 key 8 {
     label:                              '8'
     base:                               '8'
-    shift:                              '('
+    shift, capslock:                    '('
+    shift+capslock:                     '8'
     ralt:                               '['
 }
 
 key 9 {
     label:                              '9'
     base:                               '9'
-    shift:                              ')'
+    shift, capslock:                    ')'
+    shift+capslock:                     '9'
     ralt:                               ']'
 }
 
 key 0 {
     label:                              '0'
     base:                               '0'
-    shift:                              '='
+    shift, capslock:                    '='
+    shift+capslock:                     '0'
     ralt:                               '}'
 }
 
 key SLASH {
     label:                              '\u00df'
     base:                               '\u00df'
-    capslock:                           '\u1e9e'
-    shift:                              '?'
+    shift, capslock:                    '?'
+    shift+capslock:                     '\u00df'
     ralt:                               '\\'
+    shift+ralt:                         '\u1e9e'
 }
 
 key EQUALS {
@@ -196,7 +207,8 @@
 key RIGHT_BRACKET {
     label:                              '+'
     base:                               '+'
-    shift:                              '*'
+    shift, capslock:                    '*'
+    shift+capslock:                     '+'
     ralt:                               '~'
 }
 
@@ -282,7 +294,8 @@
 key BACKSLASH {
     label:                              '#'
     base:                               '#'
-    shift:                              '\''
+    shift, capslock:                    '\''
+    shift+capslock:                     '#'
 }
 
 ### ROW 4
@@ -347,13 +360,15 @@
 key COMMA {
     label:                              ','
     base:                               ','
-    shift:                              ';'
+    shift, capslock:                    ';'
+    shift+capslock:                     ','
 }
 
 key PERIOD {
     label:                              '.'
     base:                               '.'
-    shift:                              ':'
+    shift, capslock:                    ':'
+    shift+capslock:                     '.'
 }
 
 key MINUS {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 8f5d07c..407ab5f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -245,8 +245,7 @@
     }
 
     private static boolean isArchivingEnabled() {
-        return android.content.pm.Flags.archiving()
-                || SystemProperties.getBoolean("pm.archiving.enabled", false);
+        return android.content.pm.Flags.archiving();
     }
 
     private boolean isCloneProfile(UserHandle userHandle) {
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
index 3e016f7..75c8e6e 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index d156f95..06f0059 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
index b8bb25f..b72c8db 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index d156f95..06f0059 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
index 9044208..e43f27d 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
index 36cbadc..15c86dc 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
index a279481..5be3a21 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 8b546b4..791893b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -23,7 +23,6 @@
 @Composable
 fun TwoTargetSwitchPreference(
     model: SwitchPreferenceModel,
-    icon: @Composable (() -> Unit)? = null,
     primaryEnabled: () -> Boolean = { true },
     primaryOnClick: (() -> Unit)?,
 ) {
@@ -33,7 +32,7 @@
             summary = model.summary,
             primaryEnabled = primaryEnabled,
             primaryOnClick = primaryOnClick,
-            icon = icon,
+            icon = model.icon,
         ) {
             SettingsSwitch(
                 checked = model.checked(),
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index a395266..e1e1ee5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -151,7 +151,7 @@
     }
 
     private fun isArchivingEnabled(featureFlags: FeatureFlags) =
-            featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+            featureFlags.archiving()
 
     override fun showSystemPredicate(
         userIdFlow: Flow<Int>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index 5c2d770..1f7122e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -33,11 +33,13 @@
         model = object : SwitchPreferenceModel {
             override val title = label
             override val summary = this@AppListTwoTargetSwitchItem.summary
+            override val icon = @Composable {
+                AppIcon(record.app, SettingsDimension.appIconItemSize)
+            }
             override val checked = checked
             override val changeable = changeable
             override val onCheckedChange = onCheckedChange
         },
-        icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
         primaryOnClick = onClick,
     )
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index 87cd2b8..c9934ad 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -52,6 +52,8 @@
         checked = model.checked,
     )
 
+    override val icon = model.icon
+
     override val checked = when (restrictedMode) {
         null -> ({ null })
         is NoRestricted -> model.checked
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
index e100773..1bed733 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -29,14 +29,12 @@
 @Composable
 fun RestrictedTwoTargetSwitchPreference(
     model: SwitchPreferenceModel,
-    icon: @Composable (() -> Unit)? = null,
     restrictions: Restrictions,
     primaryEnabled: () -> Boolean = { true },
     primaryOnClick: (() -> Unit)?,
 ) {
     RestrictedTwoTargetSwitchPreference(
         model = model,
-        icon = icon,
         primaryEnabled = primaryEnabled,
         primaryOnClick = primaryOnClick,
         restrictions = restrictions,
@@ -48,21 +46,19 @@
 @Composable
 internal fun RestrictedTwoTargetSwitchPreference(
     model: SwitchPreferenceModel,
-    icon: @Composable (() -> Unit)? = null,
     primaryEnabled: () -> Boolean = { true },
     primaryOnClick: (() -> Unit)?,
     restrictions: Restrictions,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     if (restrictions.isEmpty()) {
-        TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+        TwoTargetSwitchPreference(model, primaryEnabled, primaryOnClick)
         return
     }
     val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
     RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
         TwoTargetSwitchPreference(
             model = restrictedModel,
-            icon = icon,
             primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
             primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
         )
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index e09ab00..9ec5caa 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -36,14 +36,14 @@
   name: "enable_le_audio_sharing"
   namespace: "pixel_cross_device_control"
   description: "Gates whether to enable LE audio sharing"
-  bug: "305620450"
+  bug: "323125723"
 }
 
 flag {
   name: "enable_le_audio_qr_code_private_broadcast_sharing"
   namespace: "pixel_cross_device_control"
   description: "Gates whether to enable LE audio private broadcast sharing via QR code"
-  bug: "308368124"
+  bug: "323125723"
 }
 
 flag {
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 89d6ac3..3ed1724 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -53,7 +53,7 @@
     <EditText
         android:id="@+id/user_name"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/user_name_height_in_user_info_dialog"
+        android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:minWidth="200dp"
         android:layout_marginStart="6dp"
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 2bd4d02..ab04904 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -82,7 +82,6 @@
     <dimen name="add_a_photo_circled_padding">6dp</dimen>
     <dimen name="user_photo_size_in_user_info_dialog">112dp</dimen>
     <dimen name="add_a_photo_icon_size_in_user_info_dialog">32dp</dimen>
-    <dimen name="user_name_height_in_user_info_dialog">48sp</dimen>
 
     <!-- Minimum increment between density scales. -->
     <fraction name="display_density_min_scale_interval">9%</fraction>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 57fcc74..a906875 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -3,10 +3,13 @@
 import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
 
 import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -30,6 +33,7 @@
 import androidx.core.graphics.drawable.IconCompat;
 
 import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
@@ -46,14 +50,14 @@
     private static final String TAG = "BluetoothUtils";
 
     public static final boolean V = false; // verbose logging
-    public static final boolean D = true;  // regular logging
+    public static final boolean D = true; // regular logging
 
     public static final int META_INT_ERROR = -1;
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
-    private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
-            "com.google.android.gms.dck");
+    private static final Set<String> EXCLUSIVE_MANAGERS =
+            ImmutableSet.of("com.google.android.gms.dck");
 
     private static ErrorListener sErrorListener;
 
@@ -89,23 +93,23 @@
     /**
      * @param context to access resources from
      * @param cachedDevice to get class from
-     * @return pair containing the drawable and the description of the Bluetooth class
-     *         of the device.
+     * @return pair containing the drawable and the description of the Bluetooth class of the
+     *     device.
      */
-    public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
-            CachedBluetoothDevice cachedDevice) {
+    public static Pair<Drawable, String> getBtClassDrawableWithDescription(
+            Context context, CachedBluetoothDevice cachedDevice) {
         BluetoothClass btClass = cachedDevice.getBtClass();
         if (btClass != null) {
             switch (btClass.getMajorDeviceClass()) {
                 case BluetoothClass.Device.Major.COMPUTER:
-                    return new Pair<>(getBluetoothDrawable(context,
-                            com.android.internal.R.drawable.ic_bt_laptop),
+                    return new Pair<>(
+                            getBluetoothDrawable(
+                                    context, com.android.internal.R.drawable.ic_bt_laptop),
                             context.getString(R.string.bluetooth_talkback_computer));
 
                 case BluetoothClass.Device.Major.PHONE:
                     return new Pair<>(
-                            getBluetoothDrawable(context,
-                                    com.android.internal.R.drawable.ic_phone),
+                            getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone),
                             context.getString(R.string.bluetooth_talkback_phone));
 
                 case BluetoothClass.Device.Major.PERIPHERAL:
@@ -115,8 +119,8 @@
 
                 case BluetoothClass.Device.Major.IMAGING:
                     return new Pair<>(
-                            getBluetoothDrawable(context,
-                                    com.android.internal.R.drawable.ic_settings_print),
+                            getBluetoothDrawable(
+                                    context, com.android.internal.R.drawable.ic_settings_print),
                             context.getString(R.string.bluetooth_talkback_imaging));
 
                 default:
@@ -125,8 +129,9 @@
         }
 
         if (cachedDevice.isHearingAidDevice()) {
-            return new Pair<>(getBluetoothDrawable(context,
-                    com.android.internal.R.drawable.ic_bt_hearing_aid),
+            return new Pair<>(
+                    getBluetoothDrawable(
+                            context, com.android.internal.R.drawable.ic_bt_hearing_aid),
                     context.getString(R.string.bluetooth_talkback_hearing_aids));
         }
 
@@ -138,7 +143,8 @@
                 // The device should show hearing aid icon if it contains any hearing aid related
                 // profiles
                 if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
-                    return new Pair<>(getBluetoothDrawable(context, profileResId),
+                    return new Pair<>(
+                            getBluetoothDrawable(context, profileResId),
                             context.getString(R.string.bluetooth_talkback_hearing_aids));
                 }
                 if (resId == 0) {
@@ -153,42 +159,40 @@
         if (btClass != null) {
             if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
                 return new Pair<>(
-                        getBluetoothDrawable(context,
-                                com.android.internal.R.drawable.ic_bt_headset_hfp),
+                        getBluetoothDrawable(
+                                context, com.android.internal.R.drawable.ic_bt_headset_hfp),
                         context.getString(R.string.bluetooth_talkback_headset));
             }
             if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) {
                 return new Pair<>(
-                        getBluetoothDrawable(context,
-                                com.android.internal.R.drawable.ic_bt_headphones_a2dp),
+                        getBluetoothDrawable(
+                                context, com.android.internal.R.drawable.ic_bt_headphones_a2dp),
                         context.getString(R.string.bluetooth_talkback_headphone));
             }
         }
         return new Pair<>(
-                getBluetoothDrawable(context,
-                        com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
+                getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth)
+                        .mutate(),
                 context.getString(R.string.bluetooth_talkback_bluetooth));
     }
 
-    /**
-     * Get bluetooth drawable by {@code resId}
-     */
+    /** Get bluetooth drawable by {@code resId} */
     public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) {
         return context.getDrawable(resId);
     }
 
-    /**
-     * Get colorful bluetooth icon with description
-     */
-    public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
-            CachedBluetoothDevice cachedDevice) {
+    /** Get colorful bluetooth icon with description */
+    public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(
+            Context context, CachedBluetoothDevice cachedDevice) {
         final Resources resources = context.getResources();
-        final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
-                cachedDevice);
+        final Pair<Drawable, String> pair =
+                BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice);
 
         if (pair.first instanceof BitmapDrawable) {
-            return new Pair<>(new AdaptiveOutlineDrawable(
-                    resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
+            return new Pair<>(
+                    new AdaptiveOutlineDrawable(
+                            resources, ((BitmapDrawable) pair.first).getBitmap()),
+                    pair.second);
         }
 
         int hashCode;
@@ -198,15 +202,12 @@
             hashCode = cachedDevice.getAddress().hashCode();
         }
 
-        return new Pair<>(buildBtRainbowDrawable(context,
-                pair.first, hashCode), pair.second);
+        return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second);
     }
 
-    /**
-     * Build Bluetooth device icon with rainbow
-     */
-    private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
-            int hashCode) {
+    /** Build Bluetooth device icon with rainbow */
+    private static Drawable buildBtRainbowDrawable(
+            Context context, Drawable drawable, int hashCode) {
         final Resources resources = context.getResources();
 
         // Deal with normal headset
@@ -222,38 +223,37 @@
         return adaptiveIcon;
     }
 
-    /**
-     * Get bluetooth icon with description
-     */
-    public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
-            CachedBluetoothDevice cachedDevice) {
-        final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
-                context, cachedDevice);
+    /** Get bluetooth icon with description */
+    public static Pair<Drawable, String> getBtDrawableWithDescription(
+            Context context, CachedBluetoothDevice cachedDevice) {
+        final Pair<Drawable, String> pair =
+                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice);
         final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
-        final int iconSize = context.getResources().getDimensionPixelSize(
-                R.dimen.bt_nearby_icon_size);
+        final int iconSize =
+                context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size);
         final Resources resources = context.getResources();
 
         // Deal with advanced device icon
         if (isAdvancedDetailsHeader(bluetoothDevice)) {
-            final Uri iconUri = getUriMetaData(bluetoothDevice,
-                    BluetoothDevice.METADATA_MAIN_ICON);
+            final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON);
             if (iconUri != null) {
                 try {
-                    context.getContentResolver().takePersistableUriPermission(iconUri,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                    context.getContentResolver()
+                            .takePersistableUriPermission(
+                                    iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                 } catch (SecurityException e) {
                     Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e);
                 }
                 try {
-                    final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
-                            context.getContentResolver(), iconUri);
+                    final Bitmap bitmap =
+                            MediaStore.Images.Media.getBitmap(
+                                    context.getContentResolver(), iconUri);
                     if (bitmap != null) {
-                        final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
-                                iconSize, false);
+                        final Bitmap resizedBitmap =
+                                Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
                         bitmap.recycle();
-                        return new Pair<>(new BitmapDrawable(resources,
-                                resizedBitmap), pair.second);
+                        return new Pair<>(
+                                new BitmapDrawable(resources, resizedBitmap), pair.second);
                     }
                 } catch (IOException e) {
                     Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
@@ -280,8 +280,8 @@
             return true;
         }
         // The metadata is for Android S
-        String deviceType = getStringMetaData(bluetoothDevice,
-                BluetoothDevice.METADATA_DEVICE_TYPE);
+        String deviceType =
+                getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
         if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
                 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
                 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)
@@ -306,8 +306,8 @@
             return true;
         }
         // The metadata is for Android S
-        String deviceType = getStringMetaData(bluetoothDevice,
-                BluetoothDevice.METADATA_DEVICE_TYPE);
+        String deviceType =
+                getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
         if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
             Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
             return true;
@@ -321,15 +321,15 @@
      * @param device Must be one of the public constants in {@link BluetoothClass.Device}
      * @return true if device class matches, false otherwise.
      */
-    public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice,
-            int device) {
+    public static boolean isDeviceClassMatched(
+            @NonNull BluetoothDevice bluetoothDevice, int device) {
         final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass();
         return bluetoothClass != null && bluetoothClass.getDeviceClass() == device;
     }
 
     private static boolean isAdvancedHeaderEnabled() {
-        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
-                true)) {
+        if (!DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) {
             Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
             return false;
         }
@@ -345,9 +345,7 @@
         return false;
     }
 
-    /**
-     * Create an Icon pointing to a drawable.
-     */
+    /** Create an Icon pointing to a drawable. */
     public static IconCompat createIconWithDrawable(Drawable drawable) {
         Bitmap bitmap;
         if (drawable instanceof BitmapDrawable) {
@@ -355,19 +353,15 @@
         } else {
             final int width = drawable.getIntrinsicWidth();
             final int height = drawable.getIntrinsicHeight();
-            bitmap = createBitmap(drawable,
-                    width > 0 ? width : 1,
-                    height > 0 ? height : 1);
+            bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
         }
         return IconCompat.createWithBitmap(bitmap);
     }
 
-    /**
-     * Build device icon with advanced outline
-     */
+    /** Build device icon with advanced outline */
     public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
-        final int iconSize = context.getResources().getDimensionPixelSize(
-                R.dimen.advanced_icon_size);
+        final int iconSize =
+                context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size);
         final Resources resources = context.getResources();
 
         Bitmap bitmap = null;
@@ -376,14 +370,12 @@
         } else {
             final int width = drawable.getIntrinsicWidth();
             final int height = drawable.getIntrinsicHeight();
-            bitmap = createBitmap(drawable,
-                    width > 0 ? width : 1,
-                    height > 0 ? height : 1);
+            bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
         }
 
         if (bitmap != null) {
-            final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
-                    iconSize, false);
+            final Bitmap resizedBitmap =
+                    Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
             bitmap.recycle();
             return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED);
         }
@@ -391,9 +383,7 @@
         return drawable;
     }
 
-    /**
-     * Creates a drawable with specified width and height.
-     */
+    /** Creates a drawable with specified width and height. */
     public static Bitmap createBitmap(Drawable drawable, int width, int height) {
         final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         final Canvas canvas = new Canvas(bitmap);
@@ -487,11 +477,8 @@
     }
 
     /**
-     * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means:
-     * 1) currently connected
-     * 2) is Hearing Aid or LE Audio
-     *    OR
-     * 3) connected profile matches currentAudioProfile
+     * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently
+     * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile
      *
      * @param cachedDevice the CachedBluetoothDevice
      * @param audioManager audio manager to get the current audio profile
@@ -519,8 +506,11 @@
             // It would show in Available Devices group.
             if (cachedDevice.isConnectedAshaHearingAidDevice()
                     || cachedDevice.isConnectedLeAudioDevice()) {
-                Log.d(TAG, "isFilterMatched() device : "
-                        + cachedDevice.getName() + ", the profile is connected.");
+                Log.d(
+                        TAG,
+                        "isFilterMatched() device : "
+                                + cachedDevice.getName()
+                                + ", the profile is connected.");
                 return true;
             }
             // According to the current audio profile type,
@@ -541,11 +531,79 @@
         return isFilterMatched;
     }
 
+    /** Returns if the le audio sharing is enabled. */
+    public static boolean isAudioSharingEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        return Flags.enableLeAudioSharing()
+                && adapter.isLeAudioBroadcastSourceSupported()
+                        == BluetoothStatusCodes.FEATURE_SUPPORTED
+                && adapter.isLeAudioBroadcastAssistantSupported()
+                        == BluetoothStatusCodes.FEATURE_SUPPORTED;
+    }
+
+    /** Returns if the broadcast is on-going. */
+    @WorkerThread
+    public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
+        if (manager == null) return false;
+        LocalBluetoothLeBroadcast broadcast =
+                manager.getProfileManager().getLeAudioBroadcastProfile();
+        return broadcast != null && broadcast.isEnabled(null);
+    }
+
     /**
-     * Checks if the Bluetooth device is an available hearing device, which means:
-     * 1) currently connected
-     * 2) is Hearing Aid
-     * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP)
+     * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+     *
+     * @param cachedDevice The cached bluetooth device to check.
+     * @param localBtManager The BT manager to provide BT functions.
+     * @return Whether the device has connected to a broadcast source.
+     */
+    @WorkerThread
+    public static boolean hasConnectedBroadcastSource(
+            CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
+        if (localBtManager == null) {
+            Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
+            return false;
+        }
+        LocalBluetoothLeBroadcastAssistant assistant =
+                localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        if (assistant == null) {
+            Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
+            return false;
+        }
+        List<BluetoothLeBroadcastReceiveState> sourceList =
+                assistant.getAllSources(cachedDevice.getDevice());
+        if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) {
+            Log.d(
+                    TAG,
+                    "Lead device has connected broadcast source, device = "
+                            + cachedDevice.getDevice().getAnonymizedAddress());
+            return true;
+        }
+        // Return true if member device is in broadcast.
+        for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
+            List<BluetoothLeBroadcastReceiveState> list =
+                    assistant.getAllSources(device.getDevice());
+            if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) {
+                Log.d(
+                        TAG,
+                        "Member device has connected broadcast source, device = "
+                                + device.getDevice().getAnonymizedAddress());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Checks the connectivity status based on the provided broadcast receive state. */
+    @WorkerThread
+    public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+        return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
+    }
+
+    /**
+     * Checks if the Bluetooth device is an available hearing device, which means: 1) currently
+     * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g.
+     * ASHA, HAP)
      *
      * @param cachedDevice the CachedBluetoothDevice
      * @return if the device is Available hearing device
@@ -553,19 +611,20 @@
     @WorkerThread
     public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) {
         if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) {
-            Log.d(TAG, "isFilterMatched() device : "
-                    + cachedDevice.getName() + ", the profile is connected.");
+            Log.d(
+                    TAG,
+                    "isFilterMatched() device : "
+                            + cachedDevice.getName()
+                            + ", the profile is connected.");
             return true;
         }
         return false;
     }
 
     /**
-     * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means:
-     * 1) currently connected
-     * 2) is not Hearing Aid or LE Audio
-     *    AND
-     * 3) connected profile does not match currentAudioProfile
+     * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently
+     * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match
+     * currentAudioProfile
      *
      * @param cachedDevice the CachedBluetoothDevice
      * @param audioManager audio manager to get the current audio profile
@@ -675,29 +734,28 @@
     }
 
     /**
-     * Returns the BluetoothDevice's exclusive manager
-     * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
-     * given set, otherwise null.
+     * Returns the BluetoothDevice's exclusive manager ({@link
+     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
+     * set, otherwise null.
      */
     @Nullable
     private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
-        byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
-                BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+        byte[] exclusiveManagerNameBytes =
+                bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
         if (exclusiveManagerNameBytes == null) {
-            Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
-                    + " doesn't have exclusive manager");
+            Log.d(
+                    TAG,
+                    "Bluetooth device "
+                            + bluetoothDevice.getName()
+                            + " doesn't have exclusive manager");
             return null;
         }
         String exclusiveManagerName = new String(exclusiveManagerNameBytes);
-        return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
-                : null;
+        return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
     }
 
-    /**
-     * Checks if given package is installed
-     */
-    private static boolean isPackageInstalled(Context context,
-            String packageName) {
+    /** Checks if given package is installed */
+    private static boolean isPackageInstalled(Context context, String packageName) {
         PackageManager packageManager = context.getPackageManager();
         try {
             packageManager.getPackageInfo(packageName, 0);
@@ -709,13 +767,12 @@
     }
 
     /**
-     * A BluetoothDevice is exclusively managed if
-     * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
-     * 2) the exclusive manager app name is in the allowlist.
-     * 3) the exclusive manager app is installed.
+     * A BluetoothDevice is exclusively managed if 1) it has field {@link
+     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
+     * in the allowlist. 3) the exclusive manager app is installed.
      */
-    public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
-            @NonNull BluetoothDevice bluetoothDevice) {
+    public static boolean isExclusivelyManagedBluetoothDevice(
+            @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
         String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
         if (exclusiveManagerName == null) {
             return false;
@@ -728,9 +785,7 @@
         }
     }
 
-    /**
-     * Return the allowlist for exclusive manager names.
-     */
+    /** Return the allowlist for exclusive manager names. */
     @NonNull
     public static Set<String> getExclusiveManagers() {
         return EXCLUSIVE_MANAGERS;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 64cf5c64..6c12cb7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -78,6 +78,7 @@
     public static final String ACTION_LE_AUDIO_SHARING_STATE_CHANGE =
             "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
     public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
+    public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
     public static final int BROADCAST_STATE_UNKNOWN = 0;
     public static final int BROADCAST_STATE_ON = 1;
     public static final int BROADCAST_STATE_OFF = 2;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 4e6d3cb..9b1e4b7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -254,8 +254,6 @@
     protected abstract List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName);
 
     protected final void rebuildDeviceList() {
-        mMediaDevices.clear();
-        mCurrentConnectedDevice = null;
         buildAvailableRoutes();
     }
 
@@ -524,6 +522,7 @@
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
     @SuppressWarnings("NewApi")
     private synchronized void buildAvailableRoutes() {
+        mMediaDevices.clear();
         RoutingSessionInfo activeSession = getActiveRoutingSession();
 
         for (MediaRoute2Info route : getAvailableRoutes(activeSession)) {
@@ -533,6 +532,12 @@
             }
             addMediaDevice(route, activeSession);
         }
+
+        // In practice, mMediaDevices should always have at least one route.
+        if (!mMediaDevices.isEmpty()) {
+            // First device on the list is always the first selected route.
+            mCurrentConnectedDevice = mMediaDevices.get(0);
+        }
     }
 
     private synchronized List<MediaRoute2Info> getAvailableRoutes(
@@ -643,9 +648,6 @@
         if (mediaDevice != null) {
             if (activeSession.getSelectedRoutes().contains(route.getId())) {
                 mediaDevice.setState(STATE_SELECTED);
-                if (mCurrentConnectedDevice == null) {
-                    mCurrentConnectedDevice = mediaDevice;
-                }
             }
             mMediaDevices.add(mediaDevice);
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 1246fd8..f197f9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
@@ -25,6 +26,7 @@
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -44,6 +46,9 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 public class BluetoothUtilsTest {
 
@@ -55,6 +60,16 @@
     private AudioManager mAudioManager;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private LocalBluetoothLeBroadcast mBroadcast;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private LocalBluetoothManager mLocalBluetoothManager;
+    @Mock
+    private LocalBluetoothLeBroadcastAssistant mAssistant;
+    @Mock
+    private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
 
     private Context mContext;
     private static final String STRING_METADATA = "string_metadata";
@@ -72,6 +87,9 @@
         MockitoAnnotations.initMocks(this);
 
         mContext = spy(RuntimeEnvironment.application);
+        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+        when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+        when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
     }
 
     @Test
@@ -432,6 +450,30 @@
     }
 
     @Test
+    public void testIsBroadcasting_broadcastEnabled_returnTrue() {
+        when(mBroadcast.isEnabled(any())).thenReturn(true);
+        assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true);
+    }
+
+    @Test
+    public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() {
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+        List<Long> bisSyncState = new ArrayList<>();
+        bisSyncState.add(1L);
+        when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+        List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+        sourceList.add(mLeBroadcastReceiveState);
+        when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+
+        assertThat(
+                BluetoothUtils.hasConnectedBroadcastSource(
+                        mCachedBluetoothDevice, mLocalBluetoothManager))
+                .isEqualTo(true);
+    }
+
+    @Test
     public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
         when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index f8dcec7..d793867 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -110,6 +110,17 @@
                     .setAddress("00:00:00:00:00:00")
                     .build();
 
+    private static final RoutingSessionInfo TEST_REMOTE_ROUTING_SESSION =
+            new RoutingSessionInfo.Builder("FAKE_REMOTE_ROUTING_SESSION_ID", TEST_PACKAGE_NAME)
+                    .addSelectedRoute(TEST_ID_1)
+                    .build();
+
+    private static final MediaRoute2Info TEST_REMOTE_ROUTE =
+            new MediaRoute2Info.Builder(TEST_ID_1, "REMOTE_ROUTE")
+                    .setSystemRoute(true)
+                    .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+                    .build();
+
     @Mock
     private MediaRouter2Manager mRouterManager;
     @Mock
@@ -151,7 +162,10 @@
         RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
         mInfoMediaManager.mRouterManager = mRouterManager;
         // Since test is running in Robolectric, return a fake session to avoid NPE.
-        when(mRouterManager.getRoutingSessions(anyString())).thenReturn(List.of(sessionInfo));
+        when(mRouterManager.getRoutingSessions(anyString()))
+                .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+        when(mRouterManager.getSelectedRoutes(any()))
+                .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
 
         mInfoMediaManager.startScan();
         mInfoMediaManager.stopScan();
@@ -191,52 +205,27 @@
 
     @Test
     public void onSessionReleased_shouldUpdateConnectedDevice() {
-        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
-        final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class);
-        routingSessionInfos.add(sessionInfo1);
-        final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class);
-        routingSessionInfos.add(sessionInfo2);
+        mInfoMediaManager.mRouterManager = mRouterManager;
 
-        final List<String> selectedRoutesSession1 = new ArrayList<>();
-        selectedRoutesSession1.add(TEST_ID_1);
-        when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1);
-
-        final List<String> selectedRoutesSession2 = new ArrayList<>();
-        selectedRoutesSession2.add(TEST_ID_2);
-        when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2);
-
-        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
-        final MediaRoute2Info info1 = mock(MediaRoute2Info.class);
-        when(info1.getId()).thenReturn(TEST_ID_1);
-        when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
-        final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
-        when(info2.getId()).thenReturn(TEST_ID_2);
-        when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
-        final List<MediaRoute2Info> routes = new ArrayList<>();
-        routes.add(info1);
-        routes.add(info2);
-        mShadowRouter2Manager.setAllRoutes(routes);
-        mShadowRouter2Manager.setTransferableRoutes(routes);
-
-        final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1);
-        assertThat(mediaDevice1).isNull();
-        final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2);
-        assertThat(mediaDevice2).isNull();
+        // Active routing session is last one in list.
+        when(mRouterManager.getRoutingSessions(anyString()))
+                .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION));
+        when(mRouterManager.getSelectedRoutes(TEST_SYSTEM_ROUTING_SESSION))
+                .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
+        when(mRouterManager.getSelectedRoutes(TEST_REMOTE_ROUTING_SESSION))
+                .thenReturn(List.of(TEST_REMOTE_ROUTE));
 
         mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-        final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0);
-        assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1);
-        final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1);
-        assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2);
-        // The active routing session is the last one in the list, which maps to infoDevice2.
-        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2);
+        MediaDevice remoteDevice = mInfoMediaManager.findMediaDevice(TEST_REMOTE_ROUTE.getId());
+        assertThat(remoteDevice).isNotNull();
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(remoteDevice);
 
-        routingSessionInfos.remove(sessionInfo2);
-        mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2);
-        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1);
+        when(mRouterManager.getRoutingSessions(anyString()))
+                .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+        mInfoMediaManager.mMediaRouterCallback.onSessionReleased(TEST_REMOTE_ROUTING_SESSION);
+        MediaDevice systemRoute = mInfoMediaManager.findMediaDevice(TEST_SYSTEM_ROUTE_ID);
+        assertThat(systemRoute).isNotNull();
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(systemRoute);
     }
 
     @Test
@@ -794,18 +783,16 @@
 
     @Test
     public void onSessionUpdated_shouldDispatchDeviceListAdded() {
-        final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        when(info.getId()).thenReturn(TEST_ID);
-        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(info.isSystemRoute()).thenReturn(true);
-
-        final List<MediaRoute2Info> routes = new ArrayList<>();
-        routes.add(info);
-        mShadowRouter2Manager.setAllRoutes(routes);
+        mInfoMediaManager.mRouterManager = mRouterManager;
+        // Since test is running in Robolectric, return a fake session to avoid NPE.
+        when(mRouterManager.getRoutingSessions(anyString()))
+                .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+        when(mRouterManager.getSelectedRoutes(any()))
+                .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
 
         mInfoMediaManager.registerCallback(mCallback);
 
-        mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
+        mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(TEST_SYSTEM_ROUTING_SESSION);
 
         verify(mCallback).onDeviceListAdded(any());
     }
@@ -871,7 +858,7 @@
     }
 
     @Test
-    public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() {
+    public void onRoutesUpdated_setsFirstSelectedRouteAsCurrentConnectedDevice() {
         final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
                 mock(CachedBluetoothDeviceManager.class);
 
@@ -886,14 +873,14 @@
 
         when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME))
                 .thenReturn(List.of(selectedBtSession));
+        when(mRouterManager.getSelectedRoutes(any())).thenReturn(List.of(TEST_BLUETOOTH_ROUTE));
         when(mLocalBluetoothManager.getCachedDeviceManager())
                 .thenReturn(cachedBluetoothDeviceManager);
         when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
                 .thenReturn(cachedDevice);
         mInfoMediaManager.mRouterManager = mRouterManager;
 
-        mInfoMediaManager.mMediaDevices.clear();
-        mInfoMediaManager.addMediaDevice(TEST_BLUETOOTH_ROUTE, selectedBtSession);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         MediaDevice device = mInfoMediaManager.mMediaDevices.get(0);
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f42efe2..c891dfc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -840,6 +840,8 @@
                         Settings.Secure.BIOMETRIC_APP_ENABLED,
                         Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
                         Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED,
+                        Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED,
+                        Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED,
                         Settings.Secure.BLUETOOTH_ADDR_VALID,
                         Settings.Secure.BLUETOOTH_ADDRESS,
                         Settings.Secure.BLUETOOTH_NAME,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 42952de..5ac0e44 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -50,7 +50,6 @@
 import android.os.Binder;
 import android.os.BugreportManager;
 import android.os.BugreportManager.BugreportCallback;
-import android.os.BugreportManager.BugreportCallback.BugreportErrorCode;
 import android.os.BugreportParams;
 import android.os.Bundle;
 import android.os.FileUtils;
@@ -169,6 +168,8 @@
     static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
     static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
     static final String EXTRA_INFO = "android.intent.extra.INFO";
+    static final String EXTRA_EXTRA_ATTACHMENT_URI =
+            "android.intent.extra.EXTRA_ATTACHMENT_URI";
 
     private static final int MSG_SERVICE_COMMAND = 1;
     private static final int MSG_DELAYED_SCREENSHOT = 2;
@@ -634,9 +635,10 @@
         long nonce = intent.getLongExtra(EXTRA_BUGREPORT_NONCE, 0);
         String baseName = getBugreportBaseName(bugreportType);
         String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+        Uri extraAttachment = intent.getParcelableExtra(EXTRA_EXTRA_ATTACHMENT_URI, Uri.class);
 
-        BugreportInfo info = new BugreportInfo(mContext, baseName, name,
-                shareTitle, shareDescription, bugreportType, mBugreportsDir, nonce);
+        BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
+                shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachment);
         synchronized (mLock) {
             if (info.bugreportFile.exists()) {
                 Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
@@ -1184,6 +1186,10 @@
             clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
             attachments.add(screenshotUri);
         }
+        if (info.extraAttachment != null) {
+            clipData.addItem(new ClipData.Item(null, null, null, info.extraAttachment));
+            attachments.add(info.extraAttachment);
+        }
         intent.setClipData(clipData);
         intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
 
@@ -2042,6 +2048,9 @@
          */
         final long nonce;
 
+        @Nullable
+        public Uri extraAttachment = null;
+
         private final Object mLock = new Object();
 
         /**
@@ -2049,7 +2058,8 @@
          */
         BugreportInfo(Context context, String baseName, String name,
                 @Nullable String shareTitle, @Nullable String shareDescription,
-                @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce) {
+                @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
+                @Nullable Uri extraAttachment) {
             this.context = context;
             this.name = this.initialName = name;
             this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2058,6 +2068,7 @@
             this.nonce = nonce;
             this.baseName = baseName;
             this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+            this.extraAttachment = extraAttachment;
         }
 
         void createBugreportFile() {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 648cc3b..a98625f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -40,7 +40,7 @@
             android:exported="true"
             android:label="@string/accessibility_menu_settings_name"
             android:launchMode="singleTop"
-            android:theme="@style/Theme.SettingsBase">
+            android:theme="@style/SettingsTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
 
@@ -59,4 +59,4 @@
             <action android:name="android.intent.action.VOICE_COMMAND" />
         </intent>
     </queries>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index 4169155..a138fa9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -21,6 +21,11 @@
     <item name="android:colorControlNormal">@color/colorControlNormal</item>
   </style>
 
+  <style name="SettingsTheme" parent="Theme.SettingsBase">
+    <!-- Quick fix so that the preference page doesn't render under its parent header. -->
+    <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+  </style>
+
   <!--The basic theme for service and test case only-->
   <style name="A11yMenuBaseTheme" parent="android:Theme.DeviceDefault.Light">
     <item name="android:windowActionBar">false</item>
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 2a32b58..1858c80 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -45,4 +45,5 @@
 java_aconfig_library {
     name: "com_android_systemui_flags_lib",
     aconfig_declarations: "com_android_systemui_flags",
+    sdk_version: "system_current",
 }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 112d964..2ad9854 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,6 +26,14 @@
 }
 
 flag {
+   name: "refactor_keyguard_dismiss_intent"
+   namespace: "systemui"
+   description: "Update how keyguard dismiss intents are stored."
+   bug: "275069969"
+}
+
+flag {
+
     name: "notification_heads_up_cycling"
     namespace: "systemui"
     description: "Heads-up notification cycling animation for the Notification Avalanche feature."
@@ -140,6 +148,16 @@
 }
 
 flag {
+   name: "pss_app_selector_recents_split_screen"
+   namespace: "systemui"
+   description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
+   bug: "320449039"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
     name: "notifications_background_icons"
     namespace: "systemui"
     description: "Moves part of the notification icon updates to the background."
@@ -716,3 +734,10 @@
       " Compose for the UI."
     bug: "325099249"
 }
+
+flag {
+    name: "keyboard_docking_indicator"
+    namespace: "systemui"
+    description: "Glow bar indicator reveals upon keyboard docking."
+    bug: "324600132"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index c008a1a..28e92aa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -86,16 +86,21 @@
                             if (shouldUseSplitNotificationShade) {
                                 with(notificationSection) {
                                     Notifications(
-                                        Modifier.fillMaxWidth(0.5f)
-                                            .fillMaxHeight()
-                                            .align(alignment = Alignment.TopEnd)
+                                        burnInParams = null,
+                                        modifier =
+                                            Modifier.fillMaxWidth(0.5f)
+                                                .fillMaxHeight()
+                                                .align(alignment = Alignment.TopEnd)
                                     )
                                 }
                             }
                         }
                         if (!shouldUseSplitNotificationShade) {
                             with(notificationSection) {
-                                Notifications(Modifier.weight(weight = 1f))
+                                Notifications(
+                                    burnInParams = null,
+                                    modifier = Modifier.weight(weight = 1f)
+                                )
                             }
                         }
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 091a439..b8f00dc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -86,16 +86,21 @@
                             if (shouldUseSplitNotificationShade) {
                                 with(notificationSection) {
                                     Notifications(
-                                        Modifier.fillMaxWidth(0.5f)
-                                            .fillMaxHeight()
-                                            .align(alignment = Alignment.TopEnd)
+                                        burnInParams = null,
+                                        modifier =
+                                            Modifier.fillMaxWidth(0.5f)
+                                                .fillMaxHeight()
+                                                .align(alignment = Alignment.TopEnd)
                                     )
                                 }
                             }
                         }
                         if (!shouldUseSplitNotificationShade) {
                             with(notificationSection) {
-                                Notifications(Modifier.weight(weight = 1f))
+                                Notifications(
+                                    burnInParams = null,
+                                    modifier = Modifier.weight(weight = 1f)
+                                )
                             }
                         }
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index 09d76a3..cba5453 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -149,6 +149,7 @@
                         if (areNotificationsVisible) {
                             with(notificationSection) {
                                 Notifications(
+                                    burnInParams = burnIn.parameters,
                                     modifier = Modifier.fillMaxWidth().weight(weight = 1f)
                                 )
                             }
@@ -375,6 +376,7 @@
                                         )
                                     }
                                 Notifications(
+                                    burnInParams = burnIn.parameters,
                                     modifier =
                                         Modifier.fillMaxHeight()
                                             .weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index a31b533..f48fa88 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -32,6 +32,9 @@
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
 import com.android.systemui.res.R
@@ -48,6 +51,7 @@
 @Inject
 constructor(
     private val viewModel: NotificationsPlaceholderViewModel,
+    private val aodBurnInViewModel: AodBurnInViewModel,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
     stackScrollLayout: NotificationStackScrollLayout,
@@ -77,8 +81,12 @@
         )
     }
 
+    /**
+     * @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
+     *   adjustment
+     */
     @Composable
-    fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+    fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
         val shouldUseSplitNotificationShade by
             lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
         val areNotificationsVisible by
@@ -97,9 +105,21 @@
         ConstrainedNotificationStack(
             viewModel = viewModel,
             modifier =
-                modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
-                    Modifier.padding(top = splitShadeTopMargin)
-                },
+                modifier
+                    .fillMaxWidth()
+                    .thenIf(shouldUseSplitNotificationShade) {
+                        Modifier.padding(top = splitShadeTopMargin)
+                    }
+                    .let {
+                        if (burnInParams == null) {
+                            it
+                        } else {
+                            it.burnInAware(
+                                viewModel = aodBurnInViewModel,
+                                params = burnInParams,
+                            )
+                        }
+                    },
         )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
new file mode 100644
index 0000000..ca6b343
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.qs.ui.composable
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+
+@Composable
+fun BrightnessMirror(
+    viewModel: BrightnessMirrorViewModel,
+    qsSceneAdapter: QSSceneAdapter,
+    modifier: Modifier = Modifier,
+) {
+    val isShowing by viewModel.isShowing.collectAsState()
+    val mirrorAlpha by
+        animateFloatAsState(
+            targetValue = if (isShowing) 1f else 0f,
+            label = "alphaAnimationBrightnessMirrorShowing",
+        )
+    val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState()
+    val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
+
+    Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
+        QuickSettingsTheme {
+            // The assumption for using this AndroidView is that there will be only one in view at
+            // a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually
+            // `BrightnessSliderController` only supports a single mirror).
+            // The benefit of doing it like this is that if the configuration changes or QSImpl is
+            // re-inflated, it's not relevant to the composable, as we'll always get a new one.
+            AndroidView(
+                modifier =
+                    Modifier.align(Alignment.TopCenter)
+                        .offset { offset }
+                        .width { mirrorOffsetAndSize.width }
+                        .height { mirrorOffsetAndSize.height },
+                factory = { context ->
+                    val (view, controller) =
+                        BrightnessMirrorInflater.inflate(context, viewModel.sliderControllerFactory)
+                    viewModel.setToggleSlider(controller)
+                    view
+                },
+                update = { qsSceneAdapter.setBrightnessMirrorController(viewModel) },
+                onRelease = { qsSceneAdapter.setBrightnessMirrorController(null) }
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 244f480..a376834 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -19,6 +19,7 @@
 import android.view.ViewGroup
 import androidx.activity.compose.BackHandler
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.fadeIn
@@ -49,6 +50,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
@@ -120,8 +122,20 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
+    val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+    val contentAlpha by
+        animateFloatAsState(
+            targetValue = if (brightnessMirrorShowing) 0f else 1f,
+            label = "alphaAnimationBrightnessMirrorContentHiding",
+        )
+
+    BrightnessMirror(
+        viewModel = viewModel.brightnessMirrorViewModel,
+        qsSceneAdapter = viewModel.qsSceneAdapter
+    )
+
     // TODO(b/280887232): implement the real UI.
-    Box(modifier = modifier.fillMaxSize()) {
+    Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) {
         val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
 
         BackHandler(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index fcd7760..02a12e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -54,9 +54,9 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.TransitionState
 import com.android.compose.animation.scene.ValueKey
 import com.android.compose.animation.scene.animateElementFloatAsState
-import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.settingslib.Utils
 import com.android.systemui.battery.BatteryMeterView
@@ -87,10 +87,6 @@
         val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup")
     }
 
-    object Keys {
-        val transitionProgress = ValueKey("ShadeHeaderTransitionProgress")
-    }
-
     object Values {
         val ClockScale = ValueKey("ShadeHeaderClockScale")
     }
@@ -119,19 +115,17 @@
         return
     }
 
-    val formatProgress =
-        animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
-            .unsafeCompositionState(initialValue = 0f)
-
     val cutoutWidth = LocalDisplayCutout.current.width()
     val cutoutLocation = LocalDisplayCutout.current.location
 
     val useExpandedFormat by
-        remember(formatProgress) {
+        remember(cutoutLocation) {
             derivedStateOf {
-                cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
+                cutoutLocation != CutoutLocation.CENTER ||
+                    shouldUseExpandedFormat(layoutState.transitionState)
             }
         }
+
     val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
 
     // This layout assumes it is globally positioned at (0, 0) and is the
@@ -207,7 +201,7 @@
 
         val screenWidth = constraints.maxWidth
         val cutoutWidthPx = cutoutWidth.roundToPx()
-        val height = ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+        val height = CollapsedHeight.roundToPx()
         val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
 
         val startMeasurable = measurables[0][0]
@@ -261,11 +255,10 @@
         return
     }
 
-    val formatProgress =
-        animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
-            .unsafeCompositionState(initialValue = 1f)
-    val useExpandedFormat by
-        remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
+    val useExpandedFormat by remember {
+        derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
+    }
+
     val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
 
     Box(modifier = modifier) {
@@ -530,3 +523,15 @@
         modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
     )
 }
+
+private fun shouldUseExpandedFormat(state: TransitionState): Boolean {
+    return when (state) {
+        is TransitionState.Idle -> {
+            state.currentScene == Scenes.QuickSettings
+        }
+        is TransitionState.Transition -> {
+            (state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) && state.progress >= 0.5) ||
+                (state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) && state.progress < 0.5)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 85798ac..9bd6f81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.ui.composable
 
 import android.view.ViewGroup
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.clipScrollableContainer
@@ -33,6 +34,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -45,6 +47,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalDensity
@@ -70,6 +73,7 @@
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
+import com.android.systemui.qs.ui.composable.BrightnessMirror
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
@@ -302,12 +306,25 @@
         }
     }
 
+    val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+    val contentAlpha by
+        animateFloatAsState(
+            targetValue = if (brightnessMirrorShowing) 0f else 1f,
+            label = "alphaAnimationBrightnessMirrorContentHiding",
+        )
+
+    val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
+
     Box(
         modifier =
             modifier
                 .fillMaxSize()
                 .element(Shade.Elements.BackgroundScrim)
-                .background(colorResource(R.color.shade_scrim_background_dark))
+                // Cannot set the alpha of the whole element to 0, because the mirror should be
+                // in the QS column.
+                .background(
+                    colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha)
+                )
     ) {
         Column(
             modifier = Modifier.fillMaxSize(),
@@ -317,61 +334,80 @@
                 createTintedIconManager = createTintedIconManager,
                 createBatteryMeterViewController = createBatteryMeterViewController,
                 statusBarIconController = statusBarIconController,
-                modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+                modifier =
+                    Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+                        .then(brightnessMirrorShowingModifier)
             )
 
             Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
-                Column(
-                    verticalArrangement = Arrangement.Top,
-                    modifier =
-                        Modifier.weight(1f).fillMaxSize().thenIf(!isCustomizing) {
-                            Modifier.padding(bottom = navBarBottomHeight)
-                        },
-                ) {
+                Box(modifier = Modifier.weight(1f)) {
+                    BrightnessMirror(
+                        viewModel = viewModel.brightnessMirrorViewModel,
+                        qsSceneAdapter = viewModel.qsSceneAdapter,
+                        // Need to remove the offset of the header height, as the mirror uses
+                        // the position of the Brightness slider in the window
+                        modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight)
+                    )
                     Column(
+                        verticalArrangement = Arrangement.Top,
                         modifier =
-                            Modifier.fillMaxSize().weight(1f).thenIf(!isCustomizing) {
-                                Modifier.verticalNestedScrollToScene()
-                                    .verticalScroll(
-                                        quickSettingsScrollState,
-                                        enabled = isScrollable
-                                    )
-                                    .clipScrollableContainer(Orientation.Horizontal)
-                            }
+                            Modifier.fillMaxSize().thenIf(!isCustomizing) {
+                                Modifier.padding(bottom = navBarBottomHeight)
+                            },
                     ) {
-                        Box(
+                        Column(
                             modifier =
-                                Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+                                Modifier.fillMaxSize()
+                                    .weight(1f)
+                                    .thenIf(!isCustomizing) {
+                                        Modifier.verticalNestedScrollToScene()
+                                            .verticalScroll(
+                                                quickSettingsScrollState,
+                                                enabled = isScrollable
+                                            )
+                                            .clipScrollableContainer(Orientation.Horizontal)
+                                    }
+                                    .then(brightnessMirrorShowingModifier)
                         ) {
-                            QuickSettings(
-                                qsSceneAdapter = viewModel.qsSceneAdapter,
-                                heightProvider = { viewModel.qsSceneAdapter.qsHeight },
-                                isSplitShade = true,
+                            Box(
+                                modifier =
+                                    Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+                            ) {
+                                QuickSettings(
+                                    qsSceneAdapter = viewModel.qsSceneAdapter,
+                                    heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+                                    isSplitShade = true,
+                                    modifier = Modifier.fillMaxWidth(),
+                                    squishiness = tileSquishiness,
+                                )
+                            }
+
+                            MediaIfVisible(
+                                viewModel = viewModel,
+                                mediaCarouselController = mediaCarouselController,
+                                mediaHost = mediaHost,
                                 modifier = Modifier.fillMaxWidth(),
-                                squishiness = tileSquishiness,
                             )
                         }
-
-                        MediaIfVisible(
-                            viewModel = viewModel,
-                            mediaCarouselController = mediaCarouselController,
-                            mediaHost = mediaHost,
-                            modifier = Modifier.fillMaxWidth(),
+                        FooterActionsWithAnimatedVisibility(
+                            viewModel = footerActionsViewModel,
+                            isCustomizing = isCustomizing,
+                            lifecycleOwner = lifecycleOwner,
+                            modifier =
+                                Modifier.align(Alignment.CenterHorizontally)
+                                    .then(brightnessMirrorShowingModifier),
                         )
                     }
-                    FooterActionsWithAnimatedVisibility(
-                        viewModel = footerActionsViewModel,
-                        isCustomizing = isCustomizing,
-                        lifecycleOwner = lifecycleOwner,
-                        modifier = Modifier.align(Alignment.CenterHorizontally),
-                    )
                 }
 
                 NotificationScrollingStack(
                     viewModel = viewModel.notifications,
                     maxScrimTop = { 0f },
                     modifier =
-                        Modifier.weight(1f).fillMaxHeight().padding(bottom = navBarBottomHeight),
+                        Modifier.weight(1f)
+                            .fillMaxHeight()
+                            .padding(bottom = navBarBottomHeight)
+                            .then(brightnessMirrorShowingModifier),
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
new file mode 100644
index 0000000..167eb65
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * Container to create a rim around the button. Both `Expandable` and `OutlinedIconToggleButton`
+ * have antialiasing problem when used with [androidx.compose.foundation.BorderStroke] and some
+ * contrast container color.
+ */
+// TODO(b/331584069): Remove this once antialiasing bug is fixed
+@Composable
+fun BottomComponentButtonSurface(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+    Surface(
+        modifier = modifier.height(64.dp),
+        shape = RoundedCornerShape(28.dp),
+        color = MaterialTheme.colorScheme.surface,
+        content = content,
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 8f187cc..fc511e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,14 +16,12 @@
 
 package com.android.systemui.volume.panel.component.button.ui.composable
 
-import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
@@ -64,20 +62,21 @@
             verticalArrangement = Arrangement.spacedBy(12.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
-            Expandable(
-                modifier =
-                    Modifier.height(64.dp).fillMaxWidth().semantics {
-                        role = Role.Button
-                        contentDescription = label
-                    },
-                color = MaterialTheme.colorScheme.primaryContainer,
-                shape = RoundedCornerShape(28.dp),
-                contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
-                borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
-                onClick = onClick,
-            ) {
-                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
-                    Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+            BottomComponentButtonSurface {
+                Expandable(
+                    modifier =
+                        Modifier.fillMaxSize().padding(8.dp).semantics {
+                            role = Role.Button
+                            contentDescription = label
+                        },
+                    color = MaterialTheme.colorScheme.primaryContainer,
+                    shape = RoundedCornerShape(28.dp),
+                    contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                    onClick = onClick,
+                ) {
+                    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+                        Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+                    }
                 }
             }
             Text(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 51ec63b..780e3f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -16,23 +16,23 @@
 
 package com.android.systemui.volume.panel.component.button.ui.composable
 
-import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedIconToggleButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
@@ -62,26 +62,33 @@
             verticalArrangement = Arrangement.spacedBy(12.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
-            OutlinedIconToggleButton(
-                modifier =
-                    Modifier.height(64.dp).fillMaxWidth().semantics {
-                        role = Role.Switch
-                        contentDescription = label
-                    },
-                checked = viewModel.isChecked,
-                onCheckedChange = onCheckedChange,
-                shape = RoundedCornerShape(28.dp),
-                colors =
-                    IconButtonDefaults.outlinedIconToggleButtonColors(
-                        containerColor = MaterialTheme.colorScheme.surface,
-                        contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
-                        checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
-                        checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
-                    ),
-                border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
-            ) {
-                Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+            BottomComponentButtonSurface {
+                val colors =
+                    if (viewModel.isChecked) {
+                        ButtonDefaults.buttonColors(
+                            containerColor = MaterialTheme.colorScheme.primaryContainer,
+                            contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                        )
+                    } else {
+                        ButtonDefaults.buttonColors(
+                            containerColor = Color.Transparent,
+                            contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                        )
+                    }
+                Button(
+                    modifier =
+                        Modifier.fillMaxSize().padding(8.dp).semantics {
+                            role = Role.Switch
+                            contentDescription = label
+                        },
+                    onClick = { onCheckedChange(!viewModel.isChecked) },
+                    shape = RoundedCornerShape(28.dp),
+                    colors = colors
+                ) {
+                    Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+                }
             }
+
             Text(
                 modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
                 text = label,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index e2d7d11..c743314 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -46,6 +46,12 @@
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
@@ -112,10 +118,16 @@
                 horizontalArrangement = Arrangement.spacedBy(spacing)
             ) {
                 for (itemIndex in items.indices) {
+                    val item = items[itemIndex]
                     Row(
                         modifier =
                             Modifier.height(48.dp)
                                 .weight(1f)
+                                .semantics {
+                                    item.contentDescription?.let { contentDescription = it }
+                                    role = Role.Switch
+                                    selected = itemIndex == scope.selectedIndex
+                                }
                                 .clickable(
                                     interactionSource = null,
                                     indication = null,
@@ -124,7 +136,6 @@
                         horizontalArrangement = Arrangement.Center,
                         verticalAlignment = Alignment.CenterVertically,
                     ) {
-                        val item = items[itemIndex]
                         if (item.icon !== Empty) {
                             with(items[itemIndex]) { icon() }
                         }
@@ -138,7 +149,8 @@
                             start = indicatorBackgroundPadding,
                             top = labelIndicatorBackgroundSpacing,
                             end = indicatorBackgroundPadding
-                        ),
+                        )
+                        .clearAndSetSemantics {},
                 horizontalArrangement = Arrangement.spacedBy(spacing),
             ) {
                 for (itemIndex in items.indices) {
@@ -296,6 +308,7 @@
         onItemSelected: () -> Unit,
         icon: @Composable RowScope.() -> Unit = Empty,
         label: @Composable RowScope.() -> Unit = Empty,
+        contentDescription: String? = null,
     )
 }
 
@@ -317,6 +330,7 @@
         onItemSelected: () -> Unit,
         icon: @Composable RowScope.() -> Unit,
         label: @Composable RowScope.() -> Unit,
+        contentDescription: String?,
     ) {
         require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
         if (isSelected) {
@@ -327,6 +341,7 @@
                 onItemSelected = onItemSelected,
                 icon = icon,
                 label = label,
+                contentDescription = contentDescription,
             )
         )
     }
@@ -340,6 +355,7 @@
     val onItemSelected: () -> Unit,
     val icon: @Composable RowScope.() -> Unit,
     val label: @Composable RowScope.() -> Unit,
+    val contentDescription: String?,
 )
 
 private const val UNSET_OFFSET = -1
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 6673afd..eed54da 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -74,9 +74,11 @@
         }
         VolumePanelRadioButtonBar {
             for (buttonViewModel in enabledModelStates) {
+                val label = buttonViewModel.button.label.toString()
                 item(
                     isSelected = buttonViewModel.button.isChecked,
                     onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+                    contentDescription = label,
                     icon = {
                         Icon(
                             icon = buttonViewModel.button.icon,
@@ -86,7 +88,7 @@
                     label = {
                         Text(
                             modifier = Modifier.basicMarquee(),
-                            text = buttonViewModel.button.label.toString(),
+                            text = label,
                             style = MaterialTheme.typography.labelMedium,
                             color = buttonViewModel.labelColor.toColor(),
                             textAlign = TextAlign.Center,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index d31064a..19d3f59 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.volume.panel.component.volume.ui.composable
 
 import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.IconButtonColors
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -32,7 +32,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
@@ -130,24 +129,20 @@
     isTappable: Boolean,
     modifier: Modifier = Modifier
 ) {
-    if (isTappable) {
-        IconButton(
-            modifier = modifier,
-            onClick = onIconTapped,
-            colors =
-                IconButtonColors(
-                    contentColor = LocalContentColor.current,
-                    containerColor = Color.Transparent,
-                    disabledContentColor = LocalContentColor.current,
-                    disabledContainerColor = Color.Transparent,
-                ),
-            content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
-        )
-    } else {
-        Box(
-            modifier = modifier,
-            contentAlignment = Alignment.Center,
-            content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
-        )
-    }
+    val boxModifier =
+        if (isTappable) {
+                modifier.clickable(
+                    onClick = onIconTapped,
+                    interactionSource = null,
+                    indication = null
+                )
+            } else {
+                modifier
+            }
+            .fillMaxSize()
+    Box(
+        modifier = boxModifier,
+        contentAlignment = Alignment.Center,
+        content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
+    )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 36919d0..ff7293d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -32,7 +32,6 @@
 import android.hardware.face.FaceSensorProperties
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.os.CancellationSignal
-import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId.fakeInstanceId
@@ -54,7 +53,6 @@
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
-import com.android.systemui.display.data.repository.display
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
@@ -697,9 +695,7 @@
             )
             runCurrent()
 
-            displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
-            displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
-
+            displayRepository.setDefaultDisplayOff(true)
             runCurrent()
 
             assertThat(canFaceAuthRun()).isTrue()
@@ -717,10 +713,7 @@
                 )
                 runCurrent()
 
-                displayRepository.emit(
-                    setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF))
-                )
-                displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+                displayRepository.setDefaultDisplayOff(true)
             }
         }
 
@@ -827,7 +820,7 @@
         }
 
     @Test
-    fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() =
+    fun isAuthenticatedIsResetToFalseWhenTransitioningToGone() =
         testScope.runTest {
             initCollectors()
             allPreconditionsToRunFaceAuthAreTrue()
@@ -840,7 +833,13 @@
 
             assertThat(authenticated()).isTrue()
 
-            keyguardRepository.keyguardDoneAnimationsFinished()
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
 
             assertThat(authenticated()).isFalse()
         }
@@ -1161,8 +1160,7 @@
         faceLockoutResetCallback.value.onLockoutReset(0)
         bouncerRepository.setAlternateVisible(true)
         keyguardRepository.setKeyguardShowing(true)
-        displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
-        displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+        displayRepository.setDefaultDisplayOff(false)
         keyguardTransitionRepository.sendTransitionSteps(
             from = KeyguardState.AOD,
             to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 1a9ad3c..f2eb7f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.QSImpl
 import com.android.systemui.qs.dagger.QSComponent
 import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.settings.brightness.MirrorController
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -508,4 +509,21 @@
             underTest.requestCloseCustomizer()
             verify(qsImpl!!).closeCustomizer()
         }
+
+    @Test
+    fun setBrightnessMirrorController() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            val mirrorController = mock<MirrorController>()
+            underTest.setBrightnessMirrorController(mirrorController)
+
+            verify(qsImpl!!).setBrightnessMirrorController(mirrorController)
+
+            underTest.setBrightnessMirrorController(null)
+            verify(qsImpl!!).setBrightnessMirrorController(null)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d9ab3b1..139289a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -31,7 +31,9 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -87,6 +89,7 @@
             flags,
             scope = testScope.backgroundScope,
         )
+    private val sceneInteractor = kosmos.sceneInteractor
 
     private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
 
@@ -108,16 +111,18 @@
 
         underTest =
             QuickSettingsSceneViewModel(
+                brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 footerActionsViewModelFactory = footerActionsViewModelFactory,
                 footerActionsController = footerActionsController,
+                sceneInteractor = sceneInteractor,
             )
     }
 
     @Test
-    fun destinationsNotCustomizing() =
+    fun destinations_whenNotCustomizing() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
             val destinations by collectLastValue(underTest.destinationScenes)
@@ -133,7 +138,30 @@
         }
 
     @Test
-    fun destinationsCustomizing_noDestinations() =
+    fun destinations_whenNotCustomizing_withPreviousSceneLockscreen() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            qsFlexiglassAdapter.setCustomizing(false)
+            val destinations by collectLastValue(underTest.destinationScenes)
+
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val previousScene by collectLastValue(sceneInteractor.previousScene)
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(previousScene).isEqualTo(Scenes.Lockscreen)
+
+            assertThat(destinations)
+                .isEqualTo(
+                    mapOf(
+                        Back to UserActionResult(Scenes.Lockscreen),
+                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen),
+                    )
+                )
+        }
+
+    @Test
+    fun destinations_whenCustomizing_noDestinations() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
             val destinations by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 98cbda2..9856f90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -74,6 +74,7 @@
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -259,6 +260,7 @@
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
+                brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
                 mediaDataManager = mediaDataManager,
                 shadeInteractor = kosmos.shadeInteractor,
                 footerActionsController = kosmos.footerActionsController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 3d66192..ae31058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -140,4 +140,23 @@
                     ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
+
+    @Test
+    fun previousScene() =
+        testScope.runTest {
+            val underTest = kosmos.sceneContainerRepository
+            val currentScene by collectLastValue(underTest.currentScene)
+            val previousScene by collectLastValue(underTest.previousScene)
+
+            assertThat(previousScene).isNull()
+
+            val firstScene = currentScene
+            underTest.changeScene(Scenes.Shade)
+            assertThat(previousScene).isEqualTo(firstScene)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+            underTest.changeScene(Scenes.QuickSettings)
+            assertThat(previousScene).isEqualTo(Scenes.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 143c0f2..b179c30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -291,4 +291,19 @@
 
             assertThat(isVisible).isFalse()
         }
+
+    @Test
+    fun previousScene() =
+        testScope.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            val previousScene by collectLastValue(underTest.previousScene)
+            assertThat(previousScene).isNull()
+
+            val firstScene = currentScene
+            underTest.changeScene(toScene = Scenes.Shade, "reason")
+            assertThat(previousScene).isEqualTo(firstScene)
+
+            underTest.changeScene(toScene = Scenes.QuickSettings, "reason")
+            assertThat(previousScene).isEqualTo(Scenes.Shade)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
new file mode 100644
index 0000000..a1af70b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingRepositoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val underTest = BrightnessMirrorShowingRepository()
+
+    @Test
+    fun isShowing_setAndFlow() =
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+
+            assertThat(isShowing).isFalse()
+
+            underTest.setMirrorShowing(true)
+            assertThat(isShowing).isTrue()
+
+            underTest.setMirrorShowing(false)
+            assertThat(isShowing).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
new file mode 100644
index 0000000..31d6df2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val underTest =
+        BrightnessMirrorShowingInteractor(kosmos.brightnessMirrorShowingRepository)
+
+    @Test
+    fun isShowing_setAndFlow() =
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+
+            assertThat(isShowing).isFalse()
+
+            underTest.setMirrorShowing(true)
+            assertThat(isShowing).isTrue()
+
+            underTest.setMirrorShowing(false)
+            assertThat(isShowing).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
new file mode 100644
index 0000000..6de7f40
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.ui.binder
+
+import android.content.applicationContext
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorInflaterTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val themedContext =
+        ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+    @Test
+    fun inflate_sliderViewAddedToFrame() {
+        Assert.setTestThread(Thread.currentThread())
+
+        val (frame, sliderController) =
+            BrightnessMirrorInflater.inflate(
+                themedContext,
+                kosmos.brightnessSliderControllerFactory
+            )
+
+        assertThat(sliderController.rootView.parent).isSameInstanceAs(frame)
+
+        Assert.setTestThread(null)
+    }
+
+    @Test
+    fun inflate_frameAndSliderViewVisible() {
+        Assert.setTestThread(Thread.currentThread())
+
+        val (frame, sliderController) =
+            BrightnessMirrorInflater.inflate(
+                themedContext,
+                kosmos.brightnessSliderControllerFactory,
+            )
+
+        assertThat(frame.visibility).isEqualTo(View.VISIBLE)
+        assertThat(sliderController.rootView.visibility).isEqualTo(View.VISIBLE)
+
+        Assert.setTestThread(null)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
new file mode 100644
index 0000000..09fc6f9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val themedContext =
+        ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+    private val underTest =
+        with(kosmos) {
+            BrightnessMirrorViewModel(
+                brightnessMirrorShowingInteractor,
+                mainResources,
+                brightnessSliderControllerFactory,
+            )
+        }
+
+    @Test
+    fun showHideMirror_isShowing() =
+        with(kosmos) {
+            testScope.runTest {
+                val showing by collectLastValue(underTest.isShowing)
+
+                assertThat(showing).isFalse()
+
+                underTest.showMirror()
+                assertThat(showing).isTrue()
+
+                underTest.hideMirror()
+                assertThat(showing).isFalse()
+            }
+        }
+
+    @Test
+    fun setLocationInWindow_correctLocationAndSize() =
+        with(kosmos) {
+            testScope.runTest {
+                val locationAndSize by collectLastValue(underTest.locationAndSize)
+
+                val x = 20
+                val y = 100
+                val height = 50
+                val width = 200
+                val padding =
+                    mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+                val mockView =
+                    mock<View> {
+                        whenever(getLocationInWindow(any())).then {
+                            it.getArgument<IntArray>(0).apply {
+                                this[0] = x
+                                this[1] = y
+                            }
+                            Unit
+                        }
+
+                        whenever(measuredHeight).thenReturn(height)
+                        whenever(measuredWidth).thenReturn(width)
+                    }
+
+                underTest.setLocationAndSize(mockView)
+
+                assertThat(locationAndSize)
+                    .isEqualTo(
+                        // Adjust for padding around the view
+                        LocationAndSize(
+                            yOffset = y - padding,
+                            width = width + 2 * padding,
+                            height = height + 2 * padding,
+                        )
+                    )
+            }
+        }
+
+    @Test
+    fun setLocationInWindow_paddingSetToRootView() =
+        with(kosmos) {
+            Assert.setTestThread(Thread.currentThread())
+            val padding =
+                mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+            val view = mock<View>()
+
+            val (_, sliderController) =
+                BrightnessMirrorInflater.inflate(
+                    themedContext,
+                    brightnessSliderControllerFactory,
+                )
+            underTest.setToggleSlider(sliderController)
+
+            underTest.setLocationAndSize(view)
+
+            with(sliderController.rootView) {
+                assertThat(paddingBottom).isEqualTo(padding)
+                assertThat(paddingTop).isEqualTo(padding)
+                assertThat(paddingLeft).isEqualTo(padding)
+                assertThat(paddingRight).isEqualTo(padding)
+            }
+
+            Assert.setTestThread(null)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 77109d6..7a681b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
@@ -127,6 +128,7 @@
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsSceneAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
+                brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
                 mediaDataManager = mediaDataManager,
                 shadeInteractor = kosmos.shadeInteractor,
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 3c28c0e..a3cf929 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.testKosmos
@@ -67,8 +66,8 @@
     fun updateBounds() =
         testScope.runTest {
             val radius = MutableStateFlow(32)
-            val viewPosition = MutableStateFlow(ViewPosition(0, 0))
-            val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, viewPosition))
+            val leftOffset = MutableStateFlow(0)
+            val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, leftOffset))
 
             placeholderViewModel.onScrimBoundsChanged(
                 ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
@@ -83,7 +82,7 @@
                     )
                 )
 
-            viewPosition.value = ViewPosition(200, 15)
+            leftOffset.value = 200
             radius.value = 24
             placeholderViewModel.onScrimBoundsChanged(
                 ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
@@ -92,7 +91,7 @@
                 .isEqualTo(
                     ShadeScrimShape(
                         bounds =
-                            ShadeScrimBounds(left = 10f, top = 185f, right = 100f, bottom = 535f),
+                            ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
                         topRadius = 24,
                         bottomRadius = 0
                     )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 509a82d..ac8387f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -19,14 +19,17 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -136,12 +139,12 @@
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
 
-            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+            val paddingTop by collectLastValue(underTest.paddingTopDimen)
 
             configurationRepository.onAnyConfigurationChange()
 
             // Should directly use the header height (flagged off value)
-            assertThat(dimens!!.paddingTop).isEqualTo(10)
+            assertThat(paddingTop).isEqualTo(10)
         }
 
     @Test
@@ -154,11 +157,11 @@
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
 
-            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+            val paddingTop by collectLastValue(underTest.paddingTopDimen)
             configurationRepository.onAnyConfigurationChange()
 
             // Should directly use the header height (flagged on value)
-            assertThat(dimens!!.paddingTop).isEqualTo(5)
+            assertThat(paddingTop).isEqualTo(5)
         }
 
     @Test
@@ -168,11 +171,11 @@
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
 
-            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+            val paddingTop by collectLastValue(underTest.paddingTopDimen)
 
             configurationRepository.onAnyConfigurationChange()
 
-            assertThat(dimens!!.paddingTop).isEqualTo(0)
+            assertThat(paddingTop).isEqualTo(0)
         }
 
     @Test
@@ -200,9 +203,9 @@
         }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, FLAG_SCENE_CONTAINER)
     fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
         testScope.runTest {
-            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
             val headerResourceHeight = 50
             val headerHelperHeight = 100
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -219,6 +222,27 @@
         }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+    @EnableSceneContainer
+    fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() =
+        testScope.runTest {
+            val headerResourceHeight = 50
+            val headerHelperHeight = 100
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+                .thenReturn(headerHelperHeight)
+            overrideResource(R.bool.config_use_large_screen_shade_header, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+            overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(dimens!!.marginTop).isEqualTo(0)
+        }
+
+    @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
         testScope.runTest {
             mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@@ -238,6 +262,26 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+            val headerResourceHeight = 50
+            val headerHelperHeight = 100
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+                .thenReturn(headerHelperHeight)
+            overrideResource(R.bool.config_use_large_screen_shade_header, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+            overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(dimens!!.marginTop).isEqualTo(0)
+        }
+
+    @Test
     fun glanceableHubAlpha_lockscreenToHub() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.glanceableHubAlpha)
@@ -661,6 +705,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     fun translationYUpdatesOnKeyguard() =
         testScope.runTest {
             val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml
new file mode 100644
index 0000000..ed1c6c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_bugreport.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open 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="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,14h4v2h-4z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,10h4v2h-4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
index 3c31861..a706c65 100644
--- a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
@@ -19,9 +19,4 @@
     <solid android:color="@color/tv_volume_dialog_accent" />
     <size android:width="@dimen/tv_volume_seek_bar_thumb_diameter"
           android:height="@dimen/tv_volume_seek_bar_thumb_diameter" />
-    <stroke android:width="@dimen/tv_volume_seek_bar_thumb_focus_ring_width"
-            android:color="@color/tv_volume_dialog_seek_thumb_focus_ring"/>
-    <item name="android:shadowColor">@color/tv_volume_dialog_seek_thumb_shadow</item>
-    <item name="android:shadowRadius">@dimen/tv_volume_seek_bar_thumb_shadow_radius</item>
-    <item name="android:shadowDy">@dimen/tv_volume_seek_bar_thumb_shadow_dy</item>
 </shape>
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 1777bdf..dabfe9d 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -39,7 +39,6 @@
         android:layout_height="wrap_content"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0.8"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         tools:srcCompat="@tools:sample/avatars" />
@@ -60,11 +59,12 @@
         android:id="@+id/scrollView"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:fillViewport="true"
-        android:padding="24dp"
-        app:layout_constrainedHeight="true"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        android:paddingBottom="16dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="12dp"
+        android:paddingTop="24dp"
+        android:fadeScrollbars="false"
+        app:layout_constraintBottom_toTopOf="@+id/button_bar"
         app:layout_constraintEnd_toStartOf="@+id/midGuideline"
         app:layout_constraintStart_toStartOf="@id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topGuideline">
@@ -91,7 +91,7 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:textAlignment="viewStart"
-                android:paddingLeft="8dp"
+                android:paddingLeft="16dp"
                 app:layout_constraintBottom_toBottomOf="@+id/logo"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toEndOf="@+id/logo"
@@ -119,7 +119,7 @@
                 style="@style/TextAppearance.AuthCredential.Subtitle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="12dp"
+                android:layout_marginTop="16dp"
                 android:gravity="@integer/biometric_dialog_text_gravity"
                 android:paddingHorizontal="0dp"
                 android:textAlignment="viewStart"
@@ -133,6 +133,7 @@
                 android:id="@+id/customized_view_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:layout_marginTop="24dp"
                 android:gravity="center_vertical"
                 android:orientation="vertical"
                 android:visibility="gone"
@@ -148,6 +149,7 @@
                 style="@style/TextAppearance.AuthCredential.Description"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginTop="24dp"
                 android:gravity="@integer/biometric_dialog_text_gravity"
                 android:paddingHorizontal="0dp"
                 android:textAlignment="viewStart"
@@ -179,7 +181,7 @@
         android:fadingEdge="horizontal"
         android:gravity="center_horizontal"
         android:scrollHorizontally="true"
-        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintBottom_toTopOf="@+id/button_bar"
         app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
         app:layout_constraintStart_toStartOf="@+id/biometric_icon"
         app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -189,7 +191,7 @@
         android:id="@+id/button_bar"
         layout="@layout/biometric_prompt_button_bar"
         android:layout_width="0dp"
-        android:layout_height="0dp"
+        android:layout_height="wrap_content"
         app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
         app:layout_constraintEnd_toEndOf="@id/scrollView"
         app:layout_constraintStart_toStartOf="@id/scrollView"
@@ -204,14 +206,6 @@
         app:barrierDirection="top"
         app:constraint_referenced_ids="scrollView" />
 
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/buttonBarrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="top"
-        app:constraint_referenced_ids="button_bar" />
-
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/leftGuideline"
         android:layout_width="wrap_content"
@@ -238,13 +232,13 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+        app:layout_constraintGuide_end="40dp" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/topGuideline"
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:orientation="horizontal"
-        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+        app:layout_constraintGuide_begin="0dp" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 8b886a7..240abab 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -29,28 +29,31 @@
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
         app:layout_constraintStart_toStartOf="@+id/leftGuideline"
-        app:layout_constraintTop_toTopOf="@+id/topBarrier" />
+        app:layout_constraintTop_toTopOf="@+id/topBarrier"
+        app:layout_constraintWidth_max="640dp" />
 
     <include
-        layout="@layout/biometric_prompt_button_bar"
         android:id="@+id/button_bar"
+        layout="@layout/biometric_prompt_button_bar"
         android:layout_width="0dp"
-        android:layout_height="match_parent"
-        app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="40dp"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@id/panel"
-        app:layout_constraintStart_toStartOf="@id/panel"/>
+        app:layout_constraintStart_toStartOf="@id/panel" />
 
     <ScrollView
         android:id="@+id/scrollView"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:fadeScrollbars="false"
         android:fillViewport="true"
-        android:paddingBottom="36dp"
-        android:paddingHorizontal="24dp"
+        android:paddingBottom="32dp"
+        android:paddingHorizontal="32dp"
         android:paddingTop="24dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
         app:layout_constraintEnd_toEndOf="@id/panel"
         app:layout_constraintStart_toStartOf="@id/panel"
         app:layout_constraintTop_toTopOf="@+id/topGuideline"
@@ -63,10 +66,10 @@
 
             <ImageView
                 android:id="@+id/logo"
-                android:contentDescription="@string/biometric_dialog_logo"
                 android:layout_width="@dimen/biometric_prompt_logo_size"
                 android:layout_height="@dimen/biometric_prompt_logo_size"
                 android:layout_gravity="center"
+                android:contentDescription="@string/biometric_dialog_logo"
                 android:scaleType="fitXY"
                 android:visibility="visible"
                 app:layout_constraintBottom_toTopOf="@+id/logo_description"
@@ -79,6 +82,7 @@
                 style="@style/TextAppearance.AuthCredential.LogoDescription"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:paddingTop="16dp"
                 app:layout_constraintBottom_toTopOf="@+id/title"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -114,6 +118,7 @@
                 android:layout_height="wrap_content"
                 android:gravity="center_vertical"
                 android:orientation="vertical"
+                android:paddingTop="24dp"
                 android:visibility="gone"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
@@ -126,6 +131,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingTop="16dp"
+                android:textAlignment="viewStart"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -153,7 +160,7 @@
         android:fadingEdge="horizontal"
         android:gravity="center_horizontal"
         android:scrollHorizontally="true"
-        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintBottom_toTopOf="@+id/button_bar"
         app:layout_constraintEnd_toEndOf="@+id/panel"
         app:layout_constraintStart_toStartOf="@+id/panel"
         app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -172,12 +179,12 @@
 
     <!-- Try Again Button -->
     <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/buttonBarrier"
+        android:id="@+id/scrollBarrier"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:barrierAllowsGoneWidgets="false"
         app:barrierDirection="top"
-        app:constraint_referenced_ids="button_bar" />
+        app:constraint_referenced_ids="biometric_icon, button_bar" />
 
     <!-- Guidelines for setting panel border -->
     <androidx.constraintlayout.widget.Guideline
@@ -199,14 +206,14 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+        app:layout_constraintGuide_end="40dp" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/topGuideline"
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:orientation="horizontal"
-        app:layout_constraintGuide_percent="0.25171" />
+        app:layout_constraintGuide_begin="56dp" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
         android:id="@+id/biometric_icon"
@@ -216,7 +223,7 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_bias="0.8"
+        app:layout_constraintVertical_bias="1.0"
         tools:srcCompat="@tools:sample/avatars" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 810c7433..4d2310a 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -17,12 +17,13 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:theme="@style/Theme.SystemUI.Dialog"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <!-- Negative Button, reserved for app -->
     <Button
         android:id="@+id/button_negative"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        style="@style/Widget.Dialog.Button.BorderButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
@@ -36,7 +37,7 @@
     <!-- Cancel Button, replaces negative button when biometric is accepted -->
     <Button
         android:id="@+id/button_cancel"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        style="@style/Widget.Dialog.Button.BorderButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
@@ -49,7 +50,7 @@
     <!-- "Use Credential" Button, replaces if device credential is allowed -->
     <Button
         android:id="@+id/button_use_credential"
-        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        style="@style/Widget.Dialog.Button.BorderButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
@@ -61,7 +62,7 @@
     <!-- Positive Button -->
     <Button
         android:id="@+id/button_confirm"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        style="@style/Widget.Dialog.Button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
@@ -76,7 +77,7 @@
     <!-- Try Again Button -->
     <Button
         android:id="@+id/button_try_again"
-        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        style="@style/Widget.Dialog.Button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 74bf318..8e3cf4d 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -35,24 +35,26 @@
         android:id="@+id/button_bar"
         layout="@layout/biometric_prompt_button_bar"
         android:layout_width="0dp"
-        android:layout_height="match_parent"
-        app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="40dp"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@id/panel"
         app:layout_constraintStart_toStartOf="@id/panel" />
 
     <ScrollView
         android:id="@+id/scrollView"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:fillViewport="true"
+        android:fadeScrollbars="false"
         android:paddingBottom="36dp"
         android:paddingHorizontal="24dp"
         android:paddingTop="24dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@id/rightGuideline"
-        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
+        app:layout_constraintEnd_toEndOf="@id/panel"
+        app:layout_constraintStart_toStartOf="@id/panel"
         app:layout_constraintTop_toTopOf="@+id/topGuideline"
         app:layout_constraintVertical_bias="1.0">
 
@@ -68,6 +70,7 @@
                 android:layout_height="@dimen/biometric_prompt_logo_size"
                 android:layout_gravity="center"
                 android:scaleType="fitXY"
+                android:importantForAccessibility="no"
                 app:layout_constraintBottom_toTopOf="@+id/logo_description"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -79,6 +82,7 @@
                 style="@style/TextAppearance.AuthCredential.LogoDescription"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:paddingTop="8dp"
                 app:layout_constraintBottom_toTopOf="@+id/title"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -114,8 +118,8 @@
                 android:layout_height="wrap_content"
                 android:gravity="center_vertical"
                 android:orientation="vertical"
-                android:visibility="gone"
                 android:paddingTop="24dp"
+                android:visibility="gone"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -127,7 +131,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:gravity="@integer/biometric_dialog_text_gravity"
-                android:paddingTop="24dp"
+                android:textAlignment="viewStart"
+                android:paddingTop="16dp"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -154,7 +159,7 @@
         android:fadingEdge="horizontal"
         android:gravity="center_horizontal"
         android:scrollHorizontally="true"
-        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintBottom_toTopOf="@+id/button_bar"
         app:layout_constraintEnd_toEndOf="@+id/panel"
         app:layout_constraintStart_toStartOf="@+id/panel"
         app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -169,12 +174,12 @@
         app:constraint_referenced_ids="scrollView" />
 
     <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/buttonBarrier"
+        android:id="@+id/scrollBarrier"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:barrierAllowsGoneWidgets="false"
         app:barrierDirection="top"
-        app:constraint_referenced_ids="button_bar" />
+        app:constraint_referenced_ids="biometric_icon, button_bar" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/leftGuideline"
@@ -195,14 +200,14 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+        app:layout_constraintGuide_end="40dp" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/topGuideline"
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:orientation="horizontal"
-        app:layout_constraintGuide_percent="0.25" />
+        app:layout_constraintGuide_begin="119dp" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
         android:id="@+id/biometric_icon"
@@ -212,7 +217,7 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_bias="0.8"
+        app:layout_constraintVertical_bias="1.0"
         tools:srcCompat="@tools:sample/avatars" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
index 53ad9f1..30d7b0a 100644
--- a/packages/SystemUI/res/layout/record_issue_dialog.xml
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -54,6 +54,7 @@
             android:layout_weight="0"
             android:src="@drawable/ic_screenrecord"
             app:tint="?androidprv:attr/materialColorOnSurface"
+            android:importantForAccessibility="no"
             android:layout_gravity="center"
             android:layout_marginEnd="@dimen/screenrecord_option_padding" />
 
@@ -78,4 +79,44 @@
             android:layout_weight="0"
             android:contentDescription="@string/quick_settings_screen_record_label" />
     </LinearLayout>
+
+    <!-- Bug Report Switch -->
+    <LinearLayout
+        android:id="@+id/bugreport_switch_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="@dimen/screenrecord_option_icon_size"
+            android:layout_height="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="0"
+            android:src="@drawable/ic_bugreport"
+            app:tint="?androidprv:attr/materialColorOnSurface"
+            android:importantForAccessibility="no"
+            android:layout_gravity="center"
+            android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="1"
+            android:layout_gravity="fill_vertical"
+            android:gravity="center"
+            android:text="@string/qs_record_issue_bug_report"
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no"/>
+
+        <Switch
+            android:id="@+id/bugreport_switch"
+            android:layout_width="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="0"
+            android:contentDescription="@string/qs_record_issue_bug_report" />
+    </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index c988b4a..eeb64bd8 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -51,15 +51,7 @@
         <LinearLayout
             android:id="@+id/screenshot_actions"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
-            <include layout="@layout/overlay_action_chip"
-                     android:id="@+id/screenshot_share_chip"/>
-            <include layout="@layout/overlay_action_chip"
-                     android:id="@+id/screenshot_edit_chip"/>
-            <include layout="@layout/overlay_action_chip"
-                     android:id="@+id/screenshot_scroll_chip"
-                     android:visibility="gone" />
-        </LinearLayout>
+            android:layout_height="wrap_content" />
     </HorizontalScrollView>
     <View
         android:id="@+id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 52f591f..d3bafbc 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -28,9 +28,6 @@
   <dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
   <dimen name="tv_volume_dialog_row_padding">6dp</dimen>
   <dimen name="tv_volume_number_text_size">16sp</dimen>
-  <dimen name="tv_volume_seek_bar_thumb_diameter">24dp</dimen>
-  <dimen name="tv_volume_seek_bar_thumb_focus_ring_width">8dp</dimen>
+  <dimen name="tv_volume_seek_bar_thumb_diameter">16dp</dimen>
   <dimen name="tv_volume_icons_size">20dp</dimen>
-  <dimen name="tv_volume_seek_bar_thumb_shadow_radius">4.0</dimen>
-  <dimen name="tv_volume_seek_bar_thumb_shadow_dy">4.0</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 2bab3cb..5d45607 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -28,8 +28,6 @@
 
     <color name="red">#FFCC0000</color>
     <color name="tv_volume_dialog_circle">#08FFFFFF</color>
-    <color name="tv_volume_dialog_seek_thumb_focus_ring">#1AFFFFFF</color>
-    <color name="tv_volume_dialog_seek_thumb_shadow">#40000000</color>
     <color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
     <color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
     <color name="tv_volume_dialog_accent">#FFDADCE0</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2338b3e..af327d2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1091,7 +1091,7 @@
     <dimen name="remote_input_history_extra_height">60dp</dimen>
 
     <!-- Biometric Dialog values -->
-    <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
+    <dimen name="biometric_dialog_face_icon_size">54dp</dimen>
     <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
     <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
     <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
@@ -1109,6 +1109,22 @@
     <dimen name="biometric_dialog_width">240dp</dimen>
     <dimen name="biometric_dialog_height">240dp</dimen>
 
+    <!-- Dimensions for biometric prompt panel padding -->
+    <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen>
+    <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen>
+    <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen>
+    <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen>
+    <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen>
+
+    <!-- Dimensions for biometric prompt icon padding -->
+    <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen>
+    <dimen name="biometric_prompt_portrait_medium_bottom_padding">160dp</dimen>
+    <dimen name="biometric_prompt_portrait_large_screen_bottom_padding">176dp</dimen>
+    <dimen name="biometric_prompt_landscape_small_bottom_padding">192dp</dimen>
+    <dimen name="biometric_prompt_landscape_small_horizontal_padding">145dp</dimen>
+    <dimen name="biometric_prompt_landscape_medium_bottom_padding">148dp</dimen>
+    <dimen name="biometric_prompt_landscape_medium_horizontal_padding">125dp</dimen>
+
     <!-- Dimensions for biometric prompt custom content view. -->
     <dimen name="biometric_prompt_logo_size">32dp</dimen>
     <dimen name="biometric_prompt_content_corner_radius">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2446039..af661aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -872,6 +872,8 @@
     <string name="qs_record_issue_start">Start</string>
     <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
     <string name="qs_record_issue_stop">Stop</string>
+    <!-- QuickSettings: Should user take a bugreport or only share trace files [CHAR LIMIT=20] -->
+    <string name="qs_record_issue_bug_report">Bug Report</string>
 
     <!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
     <string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
@@ -1971,7 +1973,7 @@
     <!-- Content description for the clear search button in shortcut search list. [CHAR LIMIT=NONE] -->
     <string name="keyboard_shortcut_clear_text">Clear search query</string>
     <!-- The title for keyboard shortcut search list [CHAR LIMIT=25] -->
-    <string name="keyboard_shortcut_search_list_title">Shortcuts</string>
+    <string name="keyboard_shortcut_search_list_title">Keyboard Shortcuts</string>
     <!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_list_hint">Search shortcuts</string>
     <!-- The description for no shortcuts results [CHAR LIMIT=25] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9da4f79..2c9006e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -230,6 +230,8 @@
     <style name="TextAppearance.AuthCredential.Indicator" parent="TextAppearance.Material3.BodyMedium">
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="android:marqueeRepeatLimit">marquee_forever</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">marquee</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.Error">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 7af9917..d0d5caf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -32,6 +32,8 @@
 import dagger.Provides
 import java.util.concurrent.Executor
 import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.android.asCoroutineDispatcher
 
 /**
  * Dagger module with system-only dependencies for the unfold animation. The code that is used to
@@ -78,6 +80,13 @@
         @Provides
         @UnfoldBg
         @Singleton
+        fun unfoldBgDispatcher(@UnfoldBg handler: Handler): CoroutineDispatcher {
+            return handler.asCoroutineDispatcher("@UnfoldBg Dispatcher")
+        }
+
+        @Provides
+        @UnfoldBg
+        @Singleton
         fun provideBgLooper(): Looper {
             return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND)
                 .apply { start() }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 70182c1..eca932c 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -432,6 +432,7 @@
                     if (MigrateClocksToBlueprint.isEnabled) {
                         listenForDozeAmountTransition(this)
                         listenForAnyStateToAodTransition(this)
+                        listenForAnyStateToLockscreenTransition(this)
                     } else {
                         listenForDozeAmount(this)
                     }
@@ -566,6 +567,17 @@
     }
 
     @VisibleForTesting
+    internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
+        return scope.launch("$TAG#listenForAnyStateToLockscreenTransition") {
+            keyguardTransitionInteractor
+                    .transitionStepsToState(LOCKSCREEN)
+                    .filter { it.transitionState == TransitionState.STARTED }
+                    .filter { it.from != AOD }
+                    .collect { handleDoze(0f) }
+        }
+    }
+
+    @VisibleForTesting
     internal fun listenForDozing(scope: CoroutineScope): Job {
         return scope.launch("$TAG#listenForDozing") {
             combine(
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 3e03fb8..b8af59d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -401,8 +401,6 @@
         mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
         mExecutor.execute(this::startOnScreenDecorationsThread);
         mDotViewController.setUiExecutor(mExecutor);
-        mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
-                this::onFaceSensorLocationChanged);
         mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
                 () -> new ScreenDecorCommand(mScreenDecorCommandCallback));
     }
@@ -579,6 +577,8 @@
         };
         mDisplayTracker.addDisplayChangeCallback(mDisplayListener, new HandlerExecutor(mHandler));
         updateConfiguration();
+        mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
+                this::onFaceSensorLocationChanged);
         Trace.endSection();
     }
 
@@ -1320,10 +1320,18 @@
     @VisibleForTesting
     void onFaceSensorLocationChanged(Point location) {
         mLogger.onSensorLocationChanged();
+
         if (mExecutor != null) {
             mExecutor.execute(
-                    () -> updateOverlayProviderViews(
-                            new Integer[]{mFaceScanningViewId}));
+                    () -> {
+                        if (getOverlayView(mFaceScanningViewId) == null) {
+                            // face sensor location was just initialized
+                            setupDecorations();
+                        } else {
+                            updateOverlayProviderViews(new Integer[]{mFaceScanningViewId});
+                        }
+                    }
+            );
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 0f4d63c..7e96e48 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -149,7 +149,7 @@
             if (mState == STATE_ENABLING || mState == STATE_DISABLING) {
                 mValueAnimator.cancel();
             }
-            mController.enableWindowMagnificationInternal(scale, centerX, centerY,
+            mController.updateWindowMagnificationInternal(scale, centerX, centerY,
                     mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
             updateState();
             return;
@@ -159,7 +159,7 @@
 
         if (mEndSpec.equals(mStartSpec)) {
             if (mState == STATE_DISABLED) {
-                mController.enableWindowMagnificationInternal(scale, centerX, centerY,
+                mController.updateWindowMagnificationInternal(scale, centerX, centerY,
                         mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
             } else if (mState == STATE_ENABLING || mState == STATE_DISABLING) {
                 mValueAnimator.cancel();
@@ -306,7 +306,7 @@
         // If the animation is playing backwards, mStartSpec will be the final spec we would
         // like to reach.
         AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
-        mController.enableWindowMagnificationInternal(
+        mController.updateWindowMagnificationInternal(
                 spec.mScale, spec.mCenterX, spec.mCenterY,
                 mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
 
@@ -358,7 +358,7 @@
                 mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract;
         final float centerY =
                 mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract;
-        mController.enableWindowMagnificationInternal(sentScale, centerX, centerY,
+        mController.updateWindowMagnificationInternal(sentScale, centerX, centerY,
                 mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index d65cd5c..a847c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -148,7 +148,7 @@
     /**
      * SourceBound is the bound of the magnified region which projects the magnified content.
      * SourceBound's center is equal to the parameters centerX and centerY in
-     * {@link WindowMagnificationController#enableWindowMagnificationInternal(float, float, float)}}
+     * {@link WindowMagnificationController#updateWindowMagnificationInternal(float, float, float)}}
      * but it is calculated from {@link #mMagnificationFrame}'s center in the runtime.
      */
     private final Rect mSourceBounds = new Rect();
@@ -566,7 +566,7 @@
         // window size changed not caused by rotation.
         if (isActivated() && reCreateWindow) {
             deleteWindowMagnification();
-            enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
+            updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
         }
     }
 
@@ -1317,7 +1317,7 @@
     }
 
     /**
-     * Wraps {@link WindowMagnificationController#enableWindowMagnificationInternal(float, float,
+     * Wraps {@link WindowMagnificationController#updateWindowMagnificationInternal(float, float,
      * 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 animator is
@@ -1344,10 +1344,12 @@
     }
 
     /**
-     * Enables window magnification with specified parameters. If the given scale is <strong>less
-     * than or equal to 1.0f</strong>, then
+     * Updates window magnification status with specified parameters. If the given scale is
+     * <strong>less than 1.0f</strong>, then
      * {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to
-     * be consistent with the behavior of display magnification.
+     * be consistent with the behavior of display magnification. If the given scale is
+     * <strong>larger than or equal to 1.0f</strong>, and the window magnification is not activated
+     * yet, window magnification will be enabled.
      *
      * @param scale   the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to center for magnification,
@@ -1355,16 +1357,17 @@
      * @param centerY the screen-relative Y coordinate around which to center for magnification,
      *                or {@link Float#NaN} to leave unchanged.
      */
-    void enableWindowMagnificationInternal(float scale, float centerX, float centerY) {
-        enableWindowMagnificationInternal(scale, centerX, centerY, Float.NaN, Float.NaN);
+    void updateWindowMagnificationInternal(float scale, float centerX, float centerY) {
+        updateWindowMagnificationInternal(scale, centerX, centerY, Float.NaN, Float.NaN);
     }
 
     /**
-     * Enables window magnification with specified parameters. If the given scale is <strong>less
-     * than 1.0f</strong>, then
+     * Updates window magnification status with specified parameters. If the given scale is
+     * <strong>less than 1.0f</strong>, then
      * {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to
-     * be consistent with the behavior of display magnification.
-     *
+     * be consistent with the behavior of display magnification. If the given scale is
+     * <strong>larger than or equal to 1.0f</strong>, and the window magnification is not activated
+     * yet, window magnification will be enabled.
      * @param scale   the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to center for magnification,
      *                or {@link Float#NaN} to leave unchanged.
@@ -1377,7 +1380,7 @@
      *                                       between frame position Y and centerY,
      *                                       or {@link Float#NaN} to leave unchanged.
      */
-    void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+    void updateWindowMagnificationInternal(float scale, float centerX, float centerY,
                 float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
         if (Float.compare(scale, 1.0f) < 0) {
             deleteWindowMagnification();
@@ -1433,7 +1436,7 @@
             return;
         }
 
-        enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
+        updateWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
         mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
         mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 96eb4b3..475bb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -43,14 +43,14 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
 import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ActiveHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.AvailableHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ConnectedDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.SavedHearingDeviceItemFactory;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 695d04f..737805b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -27,7 +27,7 @@
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
 import com.android.systemui.res.R;
 
 import kotlin.Pair;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 7d67219..591da40 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.res.Configuration
-import android.view.Display
 import com.android.systemui.biometrics.data.repository.DisplayStateRepository
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -28,7 +27,6 @@
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.unfold.updates.FoldProvider
-import com.android.systemui.util.kotlin.sample
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -36,8 +34,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Aggregates display state information. */
@@ -129,16 +125,7 @@
         screenSizeFoldProvider.onConfigurationChange(newConfig)
     }
 
-    private val defaultDisplay =
-        displayRepository.displays.map { displays ->
-            displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY }
-        }
-
-    override val isDefaultDisplayOff =
-        displayRepository.displayChangeEvent
-            .filter { it == Display.DEFAULT_DISPLAY }
-            .sample(defaultDisplay)
-            .map { it?.state == Display.STATE_OFF }
+    override val isDefaultDisplayOff = displayRepository.defaultDisplayOff
 
     override val isLargeScreen: Flow<Boolean> = displayStateRepository.isLargeScreen
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index b2ade4f..072fe47 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -288,12 +288,14 @@
                 // set padding
                 launch {
                     viewModel.promptPadding.collect { promptPadding ->
-                        view.setPadding(
-                            promptPadding.left,
-                            promptPadding.top,
-                            promptPadding.right,
-                            promptPadding.bottom
-                        )
+                        if (!constraintBp()) {
+                            view.setPadding(
+                                promptPadding.left,
+                                promptPadding.top,
+                                promptPadding.right,
+                                promptPadding.bottom
+                            )
+                        }
                     }
                 }
 
@@ -461,6 +463,23 @@
                         }
                     }
                 }
+
+                // Retry and confirmation when finger on sensor
+                launch {
+                    combine(
+                            viewModel.canTryAgainNow,
+                            viewModel.hasFingerOnSensor,
+                            viewModel.isPendingConfirmation,
+                            ::Triple
+                        )
+                        .collect { (canRetry, fingerAcquired, pendingConfirmation) ->
+                            if (canRetry && fingerAcquired) {
+                                legacyCallback.onButtonTryAgain()
+                            } else if (pendingConfirmation && fingerAcquired) {
+                                viewModel.confirmAuthenticated()
+                            }
+                        }
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e3c0cba..f380746 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -20,7 +20,6 @@
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
 import android.graphics.Outline
-import android.graphics.Rect
 import android.transition.AutoTransition
 import android.transition.TransitionManager
 import android.util.TypedValue
@@ -47,17 +46,14 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
-import com.android.systemui.biometrics.ui.viewmodel.isBottom
 import com.android.systemui.biometrics.ui.viewmodel.isLarge
 import com.android.systemui.biometrics.ui.viewmodel.isLeft
 import com.android.systemui.biometrics.ui.viewmodel.isMedium
 import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
-import com.android.systemui.biometrics.ui.viewmodel.isRight
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
-import com.android.systemui.biometrics.ui.viewmodel.isTop
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
-import kotlin.math.min
+import kotlin.math.abs
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
@@ -97,8 +93,6 @@
         if (constraintBp()) {
             val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
             val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
-            val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
-            val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
             val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
 
             val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
@@ -121,165 +115,12 @@
 
             val largeConstraintSet = ConstraintSet()
             largeConstraintSet.clone(mediumConstraintSet)
+            largeConstraintSet.constrainMaxWidth(R.id.panel, view.width)
 
             // TODO: Investigate better way to handle 180 rotations
             val flipConstraintSet = ConstraintSet()
-            flipConstraintSet.clone(mediumConstraintSet)
-            flipConstraintSet.connect(
-                R.id.scrollView,
-                ConstraintSet.START,
-                R.id.midGuideline,
-                ConstraintSet.START
-            )
-            flipConstraintSet.connect(
-                R.id.scrollView,
-                ConstraintSet.END,
-                R.id.rightGuideline,
-                ConstraintSet.END
-            )
-            flipConstraintSet.setHorizontalBias(R.id.biometric_icon, .2f)
-
-            // Round the panel outline
-            panelView.outlineProvider =
-                object : ViewOutlineProvider() {
-                    override fun getOutline(view: View, outline: Outline) {
-                        outline.setRoundRect(
-                            0,
-                            0,
-                            view.width,
-                            view.height + cornerRadiusPx,
-                            cornerRadiusPx.toFloat()
-                        )
-                    }
-                }
 
             view.doOnLayout {
-                val windowBounds = windowManager.maximumWindowMetrics.bounds
-                val bottomInset =
-                    windowManager.maximumWindowMetrics.windowInsets
-                        .getInsets(WindowInsets.Type.navigationBars())
-                        .bottom
-
-                // TODO: Move to viewmodel
-                fun measureBounds(position: PromptPosition) {
-                    val density = windowManager.currentWindowMetrics.density
-                    val width = min((640 * density).toInt(), windowBounds.width())
-
-                    var left = -1
-                    var right = -1
-                    var bottom = -1
-                    var mid = -1
-
-                    when {
-                        position.isTop -> {
-                            // Round bottom corners
-                            panelView.outlineProvider =
-                                object : ViewOutlineProvider() {
-                                    override fun getOutline(view: View, outline: Outline) {
-                                        outline.setRoundRect(
-                                            0,
-                                            -cornerRadiusPx,
-                                            view.width,
-                                            view.height,
-                                            cornerRadiusPx.toFloat()
-                                        )
-                                    }
-                                }
-                            left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                            right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                            bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
-                        }
-                        position.isBottom -> {
-                            // Round top corners
-                            panelView.outlineProvider =
-                                object : ViewOutlineProvider() {
-                                    override fun getOutline(view: View, outline: Outline) {
-                                        outline.setRoundRect(
-                                            0,
-                                            0,
-                                            view.width,
-                                            view.height + cornerRadiusPx,
-                                            cornerRadiusPx.toFloat()
-                                        )
-                                    }
-                                }
-
-                            left = windowBounds.centerX() - width / 2
-                            right = windowBounds.centerX() - width / 2
-                            bottom = if (view.isLandscape()) bottomInset else 0
-                        }
-                        position.isLeft -> {
-                            // Round right corners
-                            panelView.outlineProvider =
-                                object : ViewOutlineProvider() {
-                                    override fun getOutline(view: View, outline: Outline) {
-                                        outline.setRoundRect(
-                                            -cornerRadiusPx,
-                                            0,
-                                            view.width,
-                                            view.height,
-                                            cornerRadiusPx.toFloat()
-                                        )
-                                    }
-                                }
-
-                            left = 0
-                            mid = (windowBounds.width() * .85).toInt() / 2
-                            right = windowBounds.width() - (windowBounds.width() * .85).toInt()
-                            bottom = if (view.isLandscape()) bottomInset else 0
-                        }
-                        position.isRight -> {
-                            // Round left corners
-                            panelView.outlineProvider =
-                                object : ViewOutlineProvider() {
-                                    override fun getOutline(view: View, outline: Outline) {
-                                        outline.setRoundRect(
-                                            0,
-                                            0,
-                                            view.width + cornerRadiusPx,
-                                            view.height,
-                                            cornerRadiusPx.toFloat()
-                                        )
-                                    }
-                                }
-
-                            left = windowBounds.width() - (windowBounds.width() * .85).toInt()
-                            right = 0
-                            bottom = if (view.isLandscape()) bottomInset else 0
-                            mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2
-                        }
-                    }
-
-                    val bounds = Rect(left, mid, right, bottom)
-                    if (bounds.shouldAdjustLeftGuideline()) {
-                        leftGuideline.setGuidelineBegin(bounds.left)
-                        smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
-                        mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
-                    }
-                    if (bounds.shouldAdjustRightGuideline()) {
-                        rightGuideline.setGuidelineEnd(bounds.right)
-                        smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
-                        mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
-                    }
-                    if (bounds.shouldAdjustBottomGuideline()) {
-                        bottomGuideline.setGuidelineEnd(bounds.bottom)
-                        smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
-                        mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
-                    }
-
-                    if (position.isBottom) {
-                        topGuideline.setGuidelinePercent(.25f)
-                        mediumConstraintSet.setGuidelinePercent(topGuideline.id, .25f)
-                    } else {
-                        topGuideline.setGuidelinePercent(0f)
-                        mediumConstraintSet.setGuidelinePercent(topGuideline.id, 0f)
-                    }
-
-                    if (mid != -1 && midGuideline != null) {
-                        midGuideline.setGuidelineBegin(mid)
-                    }
-                }
-
                 fun setVisibilities(size: PromptSize) {
                     viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
                     largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
@@ -297,36 +138,151 @@
                     }
                 }
 
+                fun roundCorners(size: PromptSize, position: PromptPosition) {
+                    var left = 0
+                    var top = 0
+                    var right = 0
+                    var bottom = 0
+                    when (size) {
+                        PromptSize.SMALL,
+                        PromptSize.MEDIUM ->
+                            when (position) {
+                                PromptPosition.Right -> {
+                                    left = 0
+                                    top = 0
+                                    right = view.width + cornerRadiusPx
+                                    bottom = view.height
+                                }
+                                PromptPosition.Left -> {
+                                    left = -cornerRadiusPx
+                                    top = 0
+                                    right = view.width
+                                    bottom = view.height
+                                }
+                                PromptPosition.Top -> {
+                                    left = 0
+                                    top = -cornerRadiusPx
+                                    right = panelView.width
+                                    bottom = view.height
+                                }
+                                PromptPosition.Bottom -> {
+                                    left = 0
+                                    top = 0
+                                    right = panelView.width
+                                    bottom = view.height + cornerRadiusPx
+                                }
+                            }
+                        PromptSize.LARGE -> {
+                            left = 0
+                            top = 0
+                            right = view.width
+                            bottom = view.height
+                        }
+                    }
+
+                    // Round the panel outline
+                    panelView.outlineProvider =
+                        object : ViewOutlineProvider() {
+                            override fun getOutline(view: View, outline: Outline) {
+                                outline.setRoundRect(
+                                    left,
+                                    top,
+                                    right,
+                                    bottom,
+                                    cornerRadiusPx.toFloat()
+                                )
+                            }
+                        }
+                }
+
                 view.repeatWhenAttached {
                     var currentSize: PromptSize? = null
 
                     lifecycleScope.launch {
+                        viewModel.guidelineBounds.collect { bounds ->
+                            if (bounds.left >= 0) {
+                                mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                                smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                            } else if (bounds.left < 0) {
+                                mediumConstraintSet.setGuidelineEnd(
+                                    leftGuideline.id,
+                                    abs(bounds.left)
+                                )
+                                smallConstraintSet.setGuidelineEnd(
+                                    leftGuideline.id,
+                                    abs(bounds.left)
+                                )
+                            }
+
+                            if (bounds.right >= 0) {
+                                mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                                smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                            } else if (bounds.right < 0) {
+                                mediumConstraintSet.setGuidelineBegin(
+                                    rightGuideline.id,
+                                    abs(bounds.right)
+                                )
+                                smallConstraintSet.setGuidelineBegin(
+                                    rightGuideline.id,
+                                    abs(bounds.right)
+                                )
+                            }
+
+                            if (midGuideline != null) {
+                                if (bounds.bottom >= 0) {
+                                    midGuideline.setGuidelineEnd(bounds.bottom)
+                                    mediumConstraintSet.setGuidelineEnd(
+                                        midGuideline.id,
+                                        bounds.bottom
+                                    )
+                                } else if (bounds.bottom < 0) {
+                                    midGuideline.setGuidelineBegin(abs(bounds.bottom))
+                                    mediumConstraintSet.setGuidelineBegin(
+                                        midGuideline.id,
+                                        abs(bounds.bottom)
+                                    )
+                                }
+                            }
+                        }
+                    }
+
+                    lifecycleScope.launch {
                         combine(viewModel.position, viewModel.size, ::Pair).collect {
                             (position, size) ->
                             view.doOnAttach {
+                                setVisibilities(size)
+
                                 if (position.isLeft) {
-                                    flipConstraintSet.applyTo(view)
-                                } else if (position.isRight) {
-                                    mediumConstraintSet.applyTo(view)
+                                    if (size.isSmall) {
+                                        flipConstraintSet.clone(smallConstraintSet)
+                                    } else {
+                                        flipConstraintSet.clone(mediumConstraintSet)
+                                    }
+
+                                    // Move all content to other panel
+                                    flipConstraintSet.connect(
+                                        R.id.scrollView,
+                                        ConstraintSet.START,
+                                        R.id.midGuideline,
+                                        ConstraintSet.START
+                                    )
+                                    flipConstraintSet.connect(
+                                        R.id.scrollView,
+                                        ConstraintSet.END,
+                                        R.id.rightGuideline,
+                                        ConstraintSet.END
+                                    )
                                 }
 
-                                measureBounds(position)
-                                setVisibilities(size)
+                                roundCorners(size, position)
+
                                 when {
                                     size.isSmall -> {
-                                        val ratio =
-                                            if (view.isLandscape()) {
-                                                (windowBounds.height() -
-                                                        bottomInset -
-                                                        viewModel.promptMargin)
-                                                    .toFloat() / windowBounds.height()
-                                            } else {
-                                                (windowBounds.height() - viewModel.promptMargin)
-                                                    .toFloat() / windowBounds.height()
-                                            }
-                                        smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
-
-                                        smallConstraintSet.applyTo(view as ConstraintLayout?)
+                                        if (position.isLeft) {
+                                            flipConstraintSet.applyTo(view)
+                                        } else {
+                                            smallConstraintSet.applyTo(view)
+                                        }
                                     }
                                     size.isMedium && currentSize.isSmall -> {
                                         val autoTransition = AutoTransition()
@@ -338,7 +294,19 @@
                                             view,
                                             autoTransition
                                         )
-                                        mediumConstraintSet.applyTo(view)
+
+                                        if (position.isLeft) {
+                                            flipConstraintSet.applyTo(view)
+                                        } else {
+                                            mediumConstraintSet.applyTo(view)
+                                        }
+                                    }
+                                    size.isMedium -> {
+                                        if (position.isLeft) {
+                                            flipConstraintSet.applyTo(view)
+                                        } else {
+                                            mediumConstraintSet.applyTo(view)
+                                        }
                                     }
                                     size.isLarge -> {
                                         val autoTransition = AutoTransition()
@@ -551,20 +519,6 @@
         }
 }
 
-private fun View.centerX(): Int {
-    return (x + width / 2).toInt()
-}
-
-private fun View.centerY(): Int {
-    return (y + height / 2).toInt()
-}
-
-private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
-
-private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
-
-private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
-
 private fun View.asVerticalAnimator(
     duration: Long,
     toY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index d8265c7..66b7d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /** Sub-binder for [BiometricPromptLayout.iconView]. */
@@ -127,14 +126,22 @@
                         if (constraintBp() && position != Rect()) {
                             val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
 
-                            if (position.left != -1) {
+                            if (position.left != 0) {
                                 iconParams.endToEnd = ConstraintSet.UNSET
                                 iconParams.leftMargin = position.left
                             }
-                            if (position.top != -1) {
+                            if (position.top != 0) {
                                 iconParams.bottomToBottom = ConstraintSet.UNSET
                                 iconParams.topMargin = position.top
                             }
+                            if (position.right != 0) {
+                                iconParams.startToStart = ConstraintSet.UNSET
+                                iconParams.rightMargin = position.right
+                            }
+                            if (position.bottom != 0) {
+                                iconParams.topToTop = ConstraintSet.UNSET
+                                iconParams.bottomMargin = position.bottom
+                            }
                             iconView.layoutParams = iconParams
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index d0c140b..8dbed5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -94,25 +94,76 @@
                     params.naturalDisplayHeight,
                     rotation.ordinal
                 )
-                rotatedBounds
+                Rect(
+                    rotatedBounds.left,
+                    rotatedBounds.top,
+                    params.logicalDisplayWidth - rotatedBounds.right,
+                    params.logicalDisplayHeight - rotatedBounds.bottom
+                )
             }
             .distinctUntilChanged()
 
     val iconPosition: Flow<Rect> =
-        combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
-            sensorBounds,
-            size,
-            modalities ->
-            // If not Udfps, icon does not change from default layout position
-            if (!modalities.hasUdfps) {
-                Rect() // Empty rect, don't offset from default position
-            } else if (size.isSmall) {
-                // When small with Udfps, only set horizontal position
-                Rect(sensorBounds.left, -1, sensorBounds.right, -1)
-            } else {
-                sensorBounds
+        combine(
+                udfpsSensorBounds,
+                promptViewModel.size,
+                promptViewModel.position,
+                promptViewModel.modalities
+            ) { sensorBounds, size, position, modalities ->
+                when (position) {
+                    PromptPosition.Bottom ->
+                        if (size.isSmall) {
+                            Rect(0, 0, 0, promptViewModel.portraitSmallBottomPadding)
+                        } else if (size.isMedium && modalities.hasUdfps) {
+                            Rect(0, 0, 0, sensorBounds.bottom)
+                        } else if (size.isMedium) {
+                            Rect(0, 0, 0, promptViewModel.portraitMediumBottomPadding)
+                        } else {
+                            // Large screen
+                            Rect(0, 0, 0, promptViewModel.portraitLargeScreenBottomPadding)
+                        }
+                    PromptPosition.Right ->
+                        if (size.isSmall || modalities.hasFaceOnly) {
+                            Rect(
+                                0,
+                                0,
+                                promptViewModel.landscapeSmallHorizontalPadding,
+                                promptViewModel.landscapeSmallBottomPadding
+                            )
+                        } else if (size.isMedium && modalities.hasUdfps) {
+                            Rect(0, 0, sensorBounds.right, sensorBounds.bottom)
+                        } else {
+                            // SFPS
+                            Rect(
+                                0,
+                                0,
+                                promptViewModel.landscapeMediumHorizontalPadding,
+                                promptViewModel.landscapeMediumBottomPadding
+                            )
+                        }
+                    PromptPosition.Left ->
+                        if (size.isSmall || modalities.hasFaceOnly) {
+                            Rect(
+                                promptViewModel.landscapeSmallHorizontalPadding,
+                                0,
+                                0,
+                                promptViewModel.landscapeSmallBottomPadding
+                            )
+                        } else if (size.isMedium && modalities.hasUdfps) {
+                            Rect(sensorBounds.left, 0, 0, sensorBounds.bottom)
+                        } else {
+                            // SFPS
+                            Rect(
+                                promptViewModel.landscapeMediumHorizontalPadding,
+                                0,
+                                0,
+                                promptViewModel.landscapeMediumBottomPadding
+                            )
+                        }
+                    PromptPosition.Top -> Rect()
+                }
             }
-        }
+            .distinctUntilChanged()
 
     /** Whether an error message is currently being shown. */
     val showingError = promptViewModel.showingError
@@ -162,10 +213,11 @@
 
     val iconSize: Flow<Pair<Int, Int>> =
         combine(
+            promptViewModel.position,
             activeAuthType,
             promptViewModel.fingerprintSensorWidth,
             promptViewModel.fingerprintSensorHeight,
-        ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
+        ) { _, activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
             if (activeAuthType == AuthType.Face) {
                 Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight)
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index fbd87fd..4e9acbd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -22,6 +22,7 @@
 import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
+import android.hardware.biometrics.BiometricFingerprintConstants
 import android.hardware.biometrics.BiometricPrompt
 import android.hardware.biometrics.Flags.customBiometricPrompt
 import android.hardware.biometrics.PromptContentView
@@ -32,6 +33,7 @@
 import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -40,6 +42,7 @@
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -66,6 +69,7 @@
     promptSelectorInteractor: PromptSelectorInteractor,
     @Application private val context: Context,
     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+    private val biometricStatusInteractor: BiometricStatusInteractor,
     private val udfpsUtils: UdfpsUtils
 ) {
     /** The set of modalities available for this prompt */
@@ -86,6 +90,36 @@
     val faceIconHeight: Int =
         context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
+    /** Padding for placing icons */
+    val portraitSmallBottomPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_portrait_small_bottom_padding
+        )
+    val portraitMediumBottomPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_portrait_medium_bottom_padding
+        )
+    val portraitLargeScreenBottomPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_portrait_large_screen_bottom_padding
+        )
+    val landscapeSmallBottomPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_landscape_small_bottom_padding
+        )
+    val landscapeSmallHorizontalPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_landscape_small_horizontal_padding
+        )
+    val landscapeMediumBottomPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_landscape_medium_bottom_padding
+        )
+    val landscapeMediumHorizontalPadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_landscape_medium_horizontal_padding
+        )
+
     val fingerprintSensorWidth: Flow<Int> =
         combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
             ->
@@ -111,9 +145,6 @@
     /** Hint for talkback directional guidance */
     val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
 
-    val promptMargin: Int =
-        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
-
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -158,6 +189,24 @@
     /** Fingerprint sensor state. */
     val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow()
 
+    /** Whether a finger has been acquired by the sensor */
+    // TODO(b/331948073): Add support for detecting SFPS finger without authentication running
+    val hasFingerBeenAcquired: Flow<Boolean> =
+        combine(biometricStatusInteractor.fingerprintAcquiredStatus, modalities) {
+                status,
+                modalities ->
+                modalities.hasSfps &&
+                    status is AcquiredFingerprintAuthenticationStatus &&
+                    status.acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+            }
+            .distinctUntilChanged()
+
+    /** Whether there is currently a finger on the sensor */
+    val hasFingerOnSensor: Flow<Boolean> =
+        combine(hasFingerBeenAcquired, _isOverlayTouched) { hasFingerBeenAcquired, overlayTouched ->
+            hasFingerBeenAcquired || overlayTouched
+        }
+
     private val _forceLargeSize = MutableStateFlow(false)
     private val _forceMediumSize = MutableStateFlow(false)
 
@@ -205,6 +254,66 @@
             }
             .distinctUntilChanged()
 
+    /** Prompt panel size padding */
+    private val smallHorizontalGuidelinePadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_small_horizontal_guideline_padding
+        )
+    private val udfpsHorizontalGuidelinePadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_udfps_horizontal_guideline_padding
+        )
+    private val udfpsMidGuidelinePadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_udfps_mid_guideline_padding
+        )
+    private val mediumHorizontalGuidelinePadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_medium_horizontal_guideline_padding
+        )
+    private val mediumMidGuidelinePadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_medium_mid_guideline_padding
+        )
+
+    /**
+     * Rect for positioning prompt guidelines (left, top, right, mid)
+     *
+     * Negative values are used to signify that guideline measuring should be flipped, measuring
+     * from opposite side of the screen
+     */
+    val guidelineBounds: Flow<Rect> =
+        combine(size, position, modalities) { size, position, modalities ->
+                if (position.isBottom) {
+                    Rect(0, 0, 0, 0)
+                } else if (position.isRight) {
+                    if (size.isSmall) {
+                        Rect(-smallHorizontalGuidelinePadding, 0, 0, 0)
+                    } else if (modalities.hasUdfps) {
+                        Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding)
+                    } else if (modalities.isEmpty) {
+                        // TODO: Temporary fix until no biometric landscape layout is added
+                        Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6)
+                    } else {
+                        Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding)
+                    }
+                } else if (position.isLeft) {
+                    if (size.isSmall) {
+                        Rect(0, 0, -smallHorizontalGuidelinePadding, 0)
+                    } else if (modalities.hasUdfps) {
+                        Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding)
+                    } else if (modalities.isEmpty) {
+                        // TODO: Temporary fix until no biometric landscape layout is added
+                        Rect(0, 0, -mediumHorizontalGuidelinePadding, -6)
+                    } else {
+                        Rect(0, 0, -mediumHorizontalGuidelinePadding, -mediumMidGuidelinePadding)
+                    }
+                } else {
+                    Rect()
+                }
+            }
+            .distinctUntilChanged()
+
     /**
      * If the API caller or the user's personal preferences require explicit confirmation after
      * successful authentication. Confirmation always required when in explicit flow.
@@ -424,7 +533,7 @@
             isAuthenticated,
             promptSelectorInteractor.isCredentialAllowed,
         ) { size, _, authState, credentialAllowed ->
-            size.isNotSmall && authState.isNotAuthenticated && credentialAllowed
+            size.isMedium && authState.isNotAuthenticated && credentialAllowed
         }
 
     private val history = PromptHistoryImpl()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index cfda75c..b72b1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -28,6 +28,7 @@
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
 import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -155,17 +156,19 @@
             ->
             val topLeft = Point(sensorLocation.left, sensorLocation.top)
 
-            if (sensorLocation.isSensorVerticalInDefaultOrientation) {
-                if (displayRotation == DisplayRotation.ROTATION_0) {
-                    topLeft.x -= bounds!!.width()
-                } else if (displayRotation == DisplayRotation.ROTATION_270) {
-                    topLeft.y -= bounds!!.height()
-                }
-            } else {
-                if (displayRotation == DisplayRotation.ROTATION_180) {
-                    topLeft.y -= bounds!!.height()
-                } else if (displayRotation == DisplayRotation.ROTATION_270) {
-                    topLeft.x -= bounds!!.width()
+            if (!constraintBp()) {
+                if (sensorLocation.isSensorVerticalInDefaultOrientation) {
+                    if (displayRotation == DisplayRotation.ROTATION_0) {
+                        topLeft.x -= bounds!!.width()
+                    } else if (displayRotation == DisplayRotation.ROTATION_270) {
+                        topLeft.y -= bounds!!.height()
+                    }
+                } else {
+                    if (displayRotation == DisplayRotation.ROTATION_180) {
+                        topLeft.y -= bounds!!.height()
+                    } else if (displayRotation == DisplayRotation.ROTATION_270) {
+                        topLeft.x -= bounds!!.width()
+                    }
                 }
             }
             defaultOverlayViewParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
index 59fc81c..f86cad5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
index 9ee582a..81fe2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.util.Log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 9c63a30..94d7af7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothAdapter.STATE_OFF
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a8d9e78..c7d171d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.os.Bundle
 import android.view.LayoutInflater
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 5d18dc1..c30aea0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel.DEBUG
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
index ea51bee..6e51915 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index cd52e0d..add1647 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index fd624d2..e65b657 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.content.Intent
 import android.content.SharedPreferences
@@ -31,15 +31,15 @@
 import com.android.systemui.Prefs
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
 import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 1c621b8..dc5efef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -30,7 +30,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.graphics.drawable.Drawable
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 56ba079..f04ba75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothDevice
 import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index fce25ec..4e28caf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 357eca3..d2caefd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -398,6 +398,7 @@
                 || mAccessibilityManager.isTouchExplorationEnabled()
                 || mDataProvider.isA11yAction()
                 || mDataProvider.isFromTrackpad()
+                || mDataProvider.isFromKeyboard()
                 || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
                     && mDataProvider.isUnfolded());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index a79a654..76b228d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.classifier;
 
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 /**
@@ -50,6 +51,14 @@
     void onBouncerHidden();
 
     /**
+     * Call this to record a KeyEvent in the {@link com.android.systemui.plugins.FalsingManager}.
+     *
+     * This may decide to only collect certain KeyEvents and ignore others. Do not assume all
+     * KeyEvents are collected.
+     */
+    void onKeyEvent(KeyEvent ev);
+
+    /**
      * Call this to record a MotionEvent in the {@link com.android.systemui.plugins.FalsingManager}.
      *
      * Be sure to call {@link #onMotionEventComplete()} after the rest of SystemUI is done with the
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index d6b9a11..dcd4195 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.classifier;
 
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import javax.inject.Inject;
@@ -23,6 +24,8 @@
 /** */
 public class FalsingCollectorFake implements FalsingCollector {
 
+    public KeyEvent lastKeyEvent = null;
+
     @Override
     public void init() {
     }
@@ -70,6 +73,11 @@
     }
 
     @Override
+    public void onKeyEvent(KeyEvent ev) {
+        lastKeyEvent = ev;
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent ev) {
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 4f4f3d0..beaa170 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import androidx.annotation.VisibleForTesting;
@@ -49,7 +50,10 @@
 
 import dagger.Lazy;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -61,6 +65,14 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final long GESTURE_PROCESSING_DELAY_MS = 100;
 
+    private final Set<Integer> mAcceptedKeycodes = new HashSet<>(Arrays.asList(
+        KeyEvent.KEYCODE_ENTER,
+        KeyEvent.KEYCODE_ESCAPE,
+        KeyEvent.KEYCODE_SHIFT_LEFT,
+        KeyEvent.KEYCODE_SHIFT_RIGHT,
+        KeyEvent.KEYCODE_SPACE
+    ));
+
     private final FalsingDataProvider mFalsingDataProvider;
     private final FalsingManager mFalsingManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -279,6 +291,14 @@
     }
 
     @Override
+    public void onKeyEvent(KeyEvent ev) {
+        // Only collect if it is an ACTION_UP action and is allow-listed
+        if (ev.getAction() == KeyEvent.ACTION_UP && mAcceptedKeycodes.contains(ev.getKeyCode())) {
+            mFalsingDataProvider.onKeyEvent(ev);
+        }
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent ev) {
         logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
         if (!mKeyguardStateController.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index c5d8c79..b289fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.classifier
 
+import android.view.KeyEvent
 import android.view.MotionEvent
 import com.android.systemui.classifier.FalsingCollectorImpl.logDebug
 import com.android.systemui.dagger.SysUISingleton
@@ -59,6 +60,10 @@
         logDebug("NOOP: onBouncerHidden")
     }
 
+    override fun onKeyEvent(ev: KeyEvent) {
+        logDebug("NOOP: onKeyEvent(${ev.action}")
+    }
+
     override fun onTouchEvent(ev: MotionEvent) {
         logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 809d5b2..1501701 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -20,6 +20,7 @@
 
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.util.DisplayMetrics;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
@@ -41,6 +42,7 @@
 public class FalsingDataProvider {
 
     private static final long MOTION_EVENT_AGE_MS = 1000;
+    private static final long KEY_EVENT_AGE_MS = 500;
     private static final long DROP_EVENT_THRESHOLD_MS = 50;
     private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
 
@@ -56,8 +58,10 @@
     private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
     private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>();
 
-    private TimeLimitedMotionEventBuffer mRecentMotionEvents =
-            new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+    private TimeLimitedInputEventBuffer<MotionEvent> mRecentMotionEvents =
+            new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
+    private final TimeLimitedInputEventBuffer<KeyEvent> mRecentKeyEvents =
+            new TimeLimitedInputEventBuffer<>(KEY_EVENT_AGE_MS);
     private List<MotionEvent> mPriorMotionEvents = new ArrayList<>();
 
     private boolean mDirty = true;
@@ -89,6 +93,10 @@
         FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
     }
 
+    void onKeyEvent(KeyEvent keyEvent) {
+        mRecentKeyEvents.add(keyEvent);
+    }
+
     void onMotionEvent(MotionEvent motionEvent) {
         List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
         FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size());
@@ -109,6 +117,10 @@
         // previous ACTION_MOVE event and when it happens, it makes some classifiers fail.
         mDropLastEvent = shouldDropEvent(motionEvent);
 
+        if (!motionEvents.isEmpty() && !mRecentKeyEvents.isEmpty()) {
+            recycleAndClearRecentKeyEvents();
+        }
+
         mRecentMotionEvents.addAll(motionEvents);
 
         FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
@@ -141,7 +153,7 @@
                     mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
 
             mPriorMotionEvents = mRecentMotionEvents;
-            mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+            mRecentMotionEvents = new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
         }
         mDropLastEvent = false;
         mA11YAction = false;
@@ -261,6 +273,13 @@
         return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
     }
 
+    /**
+     * If it's a specific set of keys as collected from {@link FalsingCollector}
+     */
+    public boolean isFromKeyboard() {
+        return !mRecentKeyEvents.isEmpty();
+    }
+
     public boolean isFromTrackpad() {
         if (mRecentMotionEvents.isEmpty()) {
             return false;
@@ -318,6 +337,14 @@
         }
     }
 
+    private void recycleAndClearRecentKeyEvents() {
+        for (KeyEvent ev : mRecentKeyEvents) {
+            ev.recycle();
+        }
+
+        mRecentKeyEvents.clear();
+    }
+
     private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) {
         List<MotionEvent> motionEvents = new ArrayList<>();
         List<PointerProperties> pointerPropertiesList = new ArrayList<>();
@@ -416,6 +443,8 @@
 
         mRecentMotionEvents.clear();
 
+        recycleAndClearRecentKeyEvents();
+
         mDirty = true;
 
         mSessionListeners.forEach(SessionListener::onSessionEnded);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
new file mode 100644
index 0000000..7627ad1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
@@ -0,0 +1,242 @@
+/*
+ * 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.classifier;
+
+import android.view.InputEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Maintains an ordered list of the last N milliseconds of InputEvents.
+ *
+ * This class is simply a convenience class designed to look like a simple list, but that
+ * automatically discards old InputEvents. It functions much like a queue - first in first out -
+ * but does not have a fixed size like a circular buffer.
+ */
+public class TimeLimitedInputEventBuffer<T extends InputEvent> implements List<T> {
+
+    private final List<T> mInputEvents;
+    private final long mMaxAgeMs;
+
+    public TimeLimitedInputEventBuffer(long maxAgeMs) {
+        super();
+        mMaxAgeMs = maxAgeMs;
+        mInputEvents = new ArrayList<>();
+    }
+
+    private void ejectOldEvents() {
+        if (mInputEvents.isEmpty()) {
+            return;
+        }
+        Iterator<T> iter = listIterator();
+        long mostRecentMs = mInputEvents.get(mInputEvents.size() - 1).getEventTime();
+        while (iter.hasNext()) {
+            T ev = iter.next();
+            if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
+                iter.remove();
+                ev.recycle();
+            }
+        }
+    }
+
+    @Override
+    public void add(int index, T element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public T remove(int index) {
+        return mInputEvents.remove(index);
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        return mInputEvents.indexOf(o);
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        return mInputEvents.lastIndexOf(o);
+    }
+
+    @Override
+    public int size() {
+        return mInputEvents.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return mInputEvents.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return mInputEvents.contains(o);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return mInputEvents.iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return mInputEvents.toArray();
+    }
+
+    @Override
+    public <T2> T2[] toArray(T2[] a) {
+        return mInputEvents.toArray(a);
+    }
+
+    @Override
+    public boolean add(T element) {
+        boolean result = mInputEvents.add(element);
+        ejectOldEvents();
+        return result;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return mInputEvents.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return mInputEvents.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends T> collection) {
+        boolean result = mInputEvents.addAll(collection);
+        ejectOldEvents();
+        return result;
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends T> elements) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return mInputEvents.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return mInputEvents.retainAll(c);
+    }
+
+    @Override
+    public void clear() {
+        mInputEvents.clear();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return mInputEvents.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return mInputEvents.hashCode();
+    }
+
+    @Override
+    public T get(int index) {
+        return mInputEvents.get(index);
+    }
+
+    @Override
+    public T set(int index, T element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ListIterator<T> listIterator() {
+        return new Iter(0);
+    }
+
+    @Override
+    public ListIterator<T> listIterator(int index) {
+        return new Iter(index);
+    }
+
+    @Override
+    public List<T> subList(int fromIndex, int toIndex) {
+        return mInputEvents.subList(fromIndex, toIndex);
+    }
+
+    class Iter implements ListIterator<T> {
+
+        private final ListIterator<T> mIterator;
+
+        Iter(int index) {
+            this.mIterator = mInputEvents.listIterator(index);
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mIterator.hasNext();
+        }
+
+        @Override
+        public T next() {
+            return mIterator.next();
+        }
+
+        @Override
+        public boolean hasPrevious() {
+            return mIterator.hasPrevious();
+        }
+
+        @Override
+        public T previous() {
+            return mIterator.previous();
+        }
+
+        @Override
+        public int nextIndex() {
+            return mIterator.nextIndex();
+        }
+
+        @Override
+        public int previousIndex() {
+            return mIterator.previousIndex();
+        }
+
+        @Override
+        public void remove() {
+            mIterator.remove();
+        }
+
+        @Override
+        public void set(T inputEvent) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void add(T element) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
deleted file mode 100644
index 51aede7..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
+++ /dev/null
@@ -1,242 +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.classifier;
-
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-/**
- * Maintains an ordered list of the last N milliseconds of MotionEvents.
- *
- * This class is simply a convenience class designed to look like a simple list, but that
- * automatically discards old MotionEvents. It functions much like a queue - first in first out -
- * but does not have a fixed size like a circular buffer.
- */
-public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
-
-    private final List<MotionEvent> mMotionEvents;
-    private final long mMaxAgeMs;
-
-    public TimeLimitedMotionEventBuffer(long maxAgeMs) {
-        super();
-        mMaxAgeMs = maxAgeMs;
-        mMotionEvents = new ArrayList<>();
-    }
-
-    private void ejectOldEvents() {
-        if (mMotionEvents.isEmpty()) {
-            return;
-        }
-        Iterator<MotionEvent> iter = listIterator();
-        long mostRecentMs = mMotionEvents.get(mMotionEvents.size() - 1).getEventTime();
-        while (iter.hasNext()) {
-            MotionEvent ev = iter.next();
-            if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
-                iter.remove();
-                ev.recycle();
-            }
-        }
-    }
-
-    @Override
-    public void add(int index, MotionEvent element) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public MotionEvent remove(int index) {
-        return mMotionEvents.remove(index);
-    }
-
-    @Override
-    public int indexOf(Object o) {
-        return mMotionEvents.indexOf(o);
-    }
-
-    @Override
-    public int lastIndexOf(Object o) {
-        return mMotionEvents.lastIndexOf(o);
-    }
-
-    @Override
-    public int size() {
-        return mMotionEvents.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return mMotionEvents.isEmpty();
-    }
-
-    @Override
-    public boolean contains(Object o) {
-        return mMotionEvents.contains(o);
-    }
-
-    @Override
-    public Iterator<MotionEvent> iterator() {
-        return mMotionEvents.iterator();
-    }
-
-    @Override
-    public Object[] toArray() {
-        return mMotionEvents.toArray();
-    }
-
-    @Override
-    public <T> T[] toArray(T[] a) {
-        return mMotionEvents.toArray(a);
-    }
-
-    @Override
-    public boolean add(MotionEvent element) {
-        boolean result = mMotionEvents.add(element);
-        ejectOldEvents();
-        return result;
-    }
-
-    @Override
-    public boolean remove(Object o) {
-        return mMotionEvents.remove(o);
-    }
-
-    @Override
-    public boolean containsAll(Collection<?> c) {
-        return mMotionEvents.containsAll(c);
-    }
-
-    @Override
-    public boolean addAll(Collection<? extends MotionEvent> collection) {
-        boolean result = mMotionEvents.addAll(collection);
-        ejectOldEvents();
-        return result;
-    }
-
-    @Override
-    public boolean addAll(int index, Collection<? extends MotionEvent> elements) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean removeAll(Collection<?> c) {
-        return mMotionEvents.removeAll(c);
-    }
-
-    @Override
-    public boolean retainAll(Collection<?> c) {
-        return mMotionEvents.retainAll(c);
-    }
-
-    @Override
-    public void clear() {
-        mMotionEvents.clear();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        return mMotionEvents.equals(o);
-    }
-
-    @Override
-    public int hashCode() {
-        return mMotionEvents.hashCode();
-    }
-
-    @Override
-    public MotionEvent get(int index) {
-        return mMotionEvents.get(index);
-    }
-
-    @Override
-    public MotionEvent set(int index, MotionEvent element) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public ListIterator<MotionEvent> listIterator() {
-        return new Iter(0);
-    }
-
-    @Override
-    public ListIterator<MotionEvent> listIterator(int index) {
-        return new Iter(index);
-    }
-
-    @Override
-    public List<MotionEvent> subList(int fromIndex, int toIndex) {
-        return mMotionEvents.subList(fromIndex, toIndex);
-    }
-
-    class Iter implements ListIterator<MotionEvent> {
-
-        private final ListIterator<MotionEvent> mIterator;
-
-        Iter(int index) {
-            this.mIterator = mMotionEvents.listIterator(index);
-        }
-
-        @Override
-        public boolean hasNext() {
-            return mIterator.hasNext();
-        }
-
-        @Override
-        public MotionEvent next() {
-            return mIterator.next();
-        }
-
-        @Override
-        public boolean hasPrevious() {
-            return mIterator.hasPrevious();
-        }
-
-        @Override
-        public MotionEvent previous() {
-            return mIterator.previous();
-        }
-
-        @Override
-        public int nextIndex() {
-            return mIterator.nextIndex();
-        }
-
-        @Override
-        public int previousIndex() {
-            return mIterator.previousIndex();
-        }
-
-        @Override
-        public void remove() {
-            mIterator.remove();
-        }
-
-        @Override
-        public void set(MotionEvent motionEvent) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void add(MotionEvent element) {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index 7dd883b..f7ea25c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -46,8 +46,25 @@
 }
 
 /** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
-fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle {
-    val listener = View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> onLayoutChanged(v) }
+fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle =
+    onLayoutChanged { v, _, _, _, _, _, _, _, _ ->
+        onLayoutChanged(v)
+    }
+
+/** Adds the [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
+fun View.onLayoutChanged(listener: View.OnLayoutChangeListener): DisposableHandle {
     addOnLayoutChangeListener(listener)
     return DisposableHandle { removeOnLayoutChangeListener(listener) }
 }
+
+/** Adds a [View.OnApplyWindowInsetsListener] and provides a [DisposableHandle] for teardown. */
+fun View.onApplyWindowInsets(listener: View.OnApplyWindowInsetsListener): DisposableHandle {
+    setOnApplyWindowInsetsListener(listener)
+    return DisposableHandle { setOnApplyWindowInsetsListener(null) }
+}
+
+/** Adds a [View.OnTouchListener] and provides a [DisposableHandle] for teardown. */
+fun View.onTouchListener(listener: View.OnTouchListener): DisposableHandle {
+    setOnTouchListener(listener)
+    return DisposableHandle { setOnTouchListener(null) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index baae986..0e04d15 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -313,11 +312,7 @@
         // or device starts going to sleep.
         merge(
                 powerInteractor.isAsleep,
-                if (KeyguardWmStateRefactor.isEnabled) {
-                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
-                } else {
-                    keyguardRepository.keyguardDoneAnimationsFinished.map { true }
-                },
+                keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE),
                 userRepository.selectedUser.map {
                     it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 },
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 1230156..4ac0c56 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -67,6 +68,9 @@
      */
     val pendingDisplay: Flow<PendingDisplay?>
 
+    /** Whether the default display is currently off. */
+    val defaultDisplayOff: Flow<Boolean>
+
     /** Represents a connected display that has not been enabled yet. */
     interface PendingDisplay {
         /** Id of the pending display. */
@@ -290,6 +294,11 @@
             }
             .debugLog("pendingDisplay")
 
+    override val defaultDisplayOff: Flow<Boolean> =
+        displays
+            .map { displays -> displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY } }
+            .map { it?.state == Display.STATE_OFF }
+
     private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
         return if (DEBUG) {
             traceEach(flowName, logcat = true, traceEmissionCount = true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 0249abd..44fd582 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -41,6 +41,9 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
+import com.android.systemui.common.ui.view.onTouchListener
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -64,6 +67,7 @@
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.util.kotlin.DisposableHandles
 import com.android.systemui.util.ui.AnimatedValue
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.stopAnimating
@@ -99,16 +103,19 @@
         keyguardViewMediator: KeyguardViewMediator?,
         mainImmediateDispatcher: CoroutineDispatcher,
     ): DisposableHandle {
-        var onLayoutChangeListener: OnLayoutChange? = null
+        val disposables = DisposableHandles()
         val childViews = mutableMapOf<Int, View>()
 
         if (KeyguardBottomAreaRefactor.isEnabled) {
-            view.setOnTouchListener { _, event ->
-                if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
-                    viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
+            disposables +=
+                view.onTouchListener { _, event ->
+                    if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+                        viewModel.setRootViewLastTapPosition(
+                            Point(event.x.toInt(), event.y.toInt())
+                        )
+                    }
+                    false
                 }
-                false
-            }
         }
 
         val burnInParams = MutableStateFlow(BurnInParameters())
@@ -117,7 +124,7 @@
                 alpha = { view.alpha },
             )
 
-        val disposableHandle =
+        disposables +=
             view.repeatWhenAttached(mainImmediateDispatcher) {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
                     launch("$TAG#occludingAppDeviceEntryMessageViewModel.message") {
@@ -346,8 +353,7 @@
             }
         }
 
-        onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
-        view.addOnLayoutChangeListener(onLayoutChangeListener)
+        disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams))
 
         // Views will be added or removed after the call to bind(). This is needed to avoid many
         // calls to findViewById
@@ -362,24 +368,21 @@
                 }
             }
         )
-
-        view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
-            val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
-            burnInParams.update { current ->
-                current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
-            }
-            insets
+        disposables += DisposableHandle {
+            view.setOnHierarchyChangeListener(null)
+            childViews.clear()
         }
 
-        return object : DisposableHandle {
-            override fun dispose() {
-                disposableHandle.dispose()
-                view.removeOnLayoutChangeListener(onLayoutChangeListener)
-                view.setOnHierarchyChangeListener(null)
-                view.setOnApplyWindowInsetsListener(null)
-                childViews.clear()
+        disposables +=
+            view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+                val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+                burnInParams.update { current ->
+                    current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+                }
+                insets
             }
-        }
+
+        return disposables
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 42bd4af..ce1aed0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -621,7 +621,9 @@
     }
 
     private suspend fun updateClockAppearance(clock: ClockController) {
-        clockController.clock = clock
+        if (!MigrateClocksToBlueprint.isEnabled) {
+            clockController.clock = clock
+        }
         val colors = wallpaperColors
         if (clockRegistry.seedColor == null && colors != null) {
             // Seed color null means users do not override any color on the clock. The default
@@ -639,6 +641,11 @@
                 if (isWallpaperDark) lightClockColor else darkClockColor
             )
         }
+        // In clock preview, we should have a seed color for clock
+        // before setting clock to clockEventController to avoid updateColor with seedColor == null
+        if (MigrateClocksToBlueprint.isEnabled) {
+            clockController.clock = clock
+        }
     }
     private fun onClockChanged() {
         if (MigrateClocksToBlueprint.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index adee7f2c..4db89d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -1200,9 +1200,7 @@
     }
 
     boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
-        return (isPlayBackInfoLocal()
-                || device.getDeviceType() != MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE)
-                && !device.isVolumeFixed();
+        return !device.isVolumeFixed();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index d247122..f08bc17 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicPackageManagerAppIconLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -102,7 +102,7 @@
 
     @Binds
     @MediaProjectionAppSelectorScope
-    fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+    fun bindAppIconLoader(impl: BasicPackageManagerAppIconLoader): BasicAppIconLoader
 
     @Binds
     @IntoSet
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
new file mode 100644
index 0000000..ca5b5f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.mediaprojection.appselector.data
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.IconFactory
+import com.android.launcher3.util.UserIconInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class BadgedAppIconLoader
+@Inject
+constructor(
+    private val basicAppIconLoader: BasicAppIconLoader,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val context: Context,
+    private val iconFactoryProvider: Provider<IconFactory>,
+) {
+
+    suspend fun loadIcon(
+        userId: Int,
+        userType: RecentTask.UserType,
+        componentName: ComponentName
+    ): Drawable? =
+        withContext(backgroundDispatcher) {
+            iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
+                val icon =
+                    basicAppIconLoader.loadIcon(userId, componentName) ?: return@withContext null
+                val userHandler = UserHandle.of(userId)
+                val iconType = getIconType(userType)
+                val options =
+                    BaseIconFactory.IconOptions().apply {
+                        setUser(UserIconInfo(userHandler, iconType))
+                    }
+                val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+                badgedIcon.newIcon(context)
+            }
+        }
+
+    private fun getIconType(userType: RecentTask.UserType): Int =
+        when (userType) {
+            RecentTask.UserType.CLONED -> UserIconInfo.TYPE_CLONED
+            RecentTask.UserType.WORK -> UserIconInfo.TYPE_WORK
+            RecentTask.UserType.PRIVATE -> UserIconInfo.TYPE_PRIVATE
+            RecentTask.UserType.STANDARD -> UserIconInfo.TYPE_MAIN
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
index b85d628..03f6f01 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
@@ -17,45 +17,29 @@
 package com.android.systemui.mediaprojection.appselector.data
 
 import android.content.ComponentName
-import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import com.android.launcher3.icons.BaseIconFactory.IconOptions
-import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.shared.system.PackageManagerWrapper
 import javax.inject.Inject
-import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
 
-interface AppIconLoader {
+interface BasicAppIconLoader {
     suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
 }
 
-class IconLoaderLibAppIconLoader
+class BasicPackageManagerAppIconLoader
 @Inject
 constructor(
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    private val context: Context,
     // Use wrapper to access hidden API that allows to get ActivityInfo for any user id
     private val packageManagerWrapper: PackageManagerWrapper,
     private val packageManager: PackageManager,
-    private val iconFactoryProvider: Provider<IconFactory>
-) : AppIconLoader {
+) : BasicAppIconLoader {
 
     override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
         withContext(backgroundDispatcher) {
-            iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
-                val activityInfo =
-                    packageManagerWrapper.getActivityInfo(component, userId)
-                        ?: return@withContext null
-                val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
-                val userHandler = UserHandle.of(userId)
-                val options = IconOptions().apply { setUser(userHandler) }
-                val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
-                badgedIcon.newIcon(context)
-            }
+            packageManagerWrapper.getActivityInfo(component, userId)?.loadIcon(packageManager)
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index e9b4582..2dbe2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -18,7 +18,9 @@
 
 import android.annotation.ColorInt
 import android.annotation.UserIdInt
+import android.app.ActivityManager.RecentTaskInfo
 import android.content.ComponentName
+import com.android.wm.shell.util.SplitBounds
 
 data class RecentTask(
     val taskId: Int,
@@ -28,4 +30,30 @@
     val baseIntentComponent: ComponentName?,
     @ColorInt val colorBackground: Int?,
     val isForegroundTask: Boolean,
-)
+    val userType: UserType,
+    val splitBounds: SplitBounds?,
+) {
+    constructor(
+        taskInfo: RecentTaskInfo,
+        isForegroundTask: Boolean,
+        userType: UserType,
+        splitBounds: SplitBounds? = null
+    ) : this(
+        taskInfo.taskId,
+        taskInfo.displayId,
+        taskInfo.userId,
+        taskInfo.topActivity,
+        taskInfo.baseIntent?.component,
+        taskInfo.taskDescription?.backgroundColor,
+        isForegroundTask,
+        userType,
+        splitBounds
+    )
+
+    enum class UserType {
+        STANDARD,
+        WORK,
+        PRIVATE,
+        CLONED
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 5dde14b..596c18f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.mediaprojection.appselector.data
 
 import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
+import android.content.pm.UserInfo
+import android.os.UserManager
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
@@ -41,7 +43,8 @@
     @Background private val coroutineDispatcher: CoroutineDispatcher,
     @Background private val backgroundExecutor: Executor,
     private val recentTasks: Optional<RecentTasks>,
-    private val userTracker: UserTracker
+    private val userTracker: UserTracker,
+    private val userManager: UserManager,
 ) : RecentTaskListProvider {
 
     private val recents by lazy { recentTasks.getOrNull() }
@@ -55,19 +58,27 @@
             val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId
             val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId
             val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2)
-            groupedTasks
-                .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
-                .map {
+            groupedTasks.flatMap {
+                val task1 =
                     RecentTask(
-                        it.taskId,
-                        it.displayId,
-                        it.userId,
-                        it.topActivity,
-                        it.baseIntent?.component,
-                        it.taskDescription?.backgroundColor,
-                        isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible
+                        it.taskInfo1,
+                        it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
+                        userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
+                        it.splitBounds
                     )
-                }
+
+                val task2 =
+                    if (it.taskInfo2 != null) {
+                        RecentTask(
+                            it.taskInfo2!!,
+                            it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
+                            userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
+                            it.splitBounds
+                        )
+                    } else null
+
+                listOfNotNull(task1, task2)
+            }
         }
 
     private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
@@ -81,4 +92,15 @@
                 continuation.resume(tasks)
             }
         }
+
+    private fun UserInfo.toUserType(): RecentTask.UserType =
+        if (isCloneProfile) {
+            RecentTask.UserType.CLONED
+        } else if (isManagedProfile) {
+            RecentTask.UserType.WORK
+        } else if (isPrivateProfile) {
+            RecentTask.UserType.PRIVATE
+        } else {
+            RecentTask.UserType.STANDARD
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 7c7efd0..9549ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -24,9 +24,12 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.window.RemoteTransition
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.Flags.pssAppSelectorAbruptExitFix
+import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen
+import com.android.systemui.display.naturalBounds
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
@@ -34,6 +37,11 @@
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.res.R
 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.util.SplitBounds
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -48,6 +56,7 @@
     private val taskViewSizeProvider: TaskPreviewSizeProvider,
     private val activityTaskManager: IActivityTaskManager,
     private val resultHandler: MediaProjectionAppSelectorResultHandler,
+    private val splitScreen: Optional<SplitScreen>,
 ) : RecentTaskClickListener, TaskPreviewSizeListener {
 
     private var views: Views? = null
@@ -63,11 +72,11 @@
     fun createView(parent: ViewGroup): ViewGroup =
         views?.root
             ?: createRecentViews(parent)
-                .also {
-                    views = it
-                    lastBoundData?.let { recents -> bind(recents) }
-                }
-                .root
+                    .also {
+                        views = it
+                        lastBoundData?.let { recents -> bind(recents) }
+                    }
+                    .root
 
     fun bind(recentTasks: List<RecentTask>) {
         views?.apply {
@@ -93,8 +102,10 @@
     private fun createRecentViews(parent: ViewGroup): Views {
         val recentsRoot =
             LayoutInflater.from(parent.context)
-                .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
-                as ViewGroup
+                    .inflate(R.layout.media_projection_recent_tasks,
+                        parent, /* attachToRoot= */
+                        false)
+                    as ViewGroup
 
         val container =
             recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
@@ -121,18 +132,34 @@
         return Views(recentsRoot, container, progress, recycler)
     }
 
+    private fun RecentTask.isLaunchingInSplitScreen(): Boolean {
+        return splitScreen.isPresent && splitBounds != null
+    }
+
     override fun onRecentAppClicked(task: RecentTask, view: View) {
         val launchCookie = LaunchCookie()
         val activityOptions = createAnimation(task, view)
         activityOptions.pendingIntentBackgroundActivityStartMode =
             MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-        activityOptions.setLaunchCookie(launchCookie)
         activityOptions.launchDisplayId = task.displayId
+        activityOptions.setLaunchCookie(launchCookie)
 
-        activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
-        resultHandler.returnSelectedApp(launchCookie)
+        val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie)}
+
+        val taskId = task.taskId
+        val splitBounds = task.splitBounds
+
+        if (pssAppSelectorRecentsSplitScreen() &&
+            task.isLaunchingInSplitScreen() &&
+            !task.isForegroundTask) {
+            startSplitScreenTask(view, taskId, splitBounds!!, handleResult, activityOptions)
+        } else {
+            activityTaskManager.startActivityFromRecents(taskId, activityOptions.toBundle())
+            handleResult()
+        }
     }
 
+
     private fun createAnimation(task: RecentTask, view: View): ActivityOptions =
         if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) {
             // When the selected task is in the foreground, the scale up animation doesn't work.
@@ -145,7 +172,14 @@
                 /* startedListener = */ null,
                 /* finishedListener = */ null
             )
+        } else if (task.isLaunchingInSplitScreen()) {
+            // When the selected task isn't in the foreground, but is launching in split screen,
+            // then we don't need to specify an animation, since we'll already be passing a
+            // manually built remote animation to SplitScreenController
+            ActivityOptions.makeBasic()
         } else {
+            // The default case is a selected task not in the foreground and launching fullscreen,
+            // so for this we can use the default ActivityOptions animation
             ActivityOptions.makeScaleUpAnimation(
                 view,
                 /* startX= */ 0,
@@ -155,6 +189,29 @@
             )
         }
 
+    private fun startSplitScreenTask(
+        view: View,
+        taskId: Int,
+        splitBounds: SplitBounds,
+        handleResult: () -> Unit,
+        activityOptions: ActivityOptions,
+    ) {
+        val isLeftTopTask = taskId == splitBounds.leftTopTaskId
+        val task2Id =
+            if (isLeftTopTask) splitBounds.rightBottomTaskId else splitBounds.leftTopTaskId
+        val splitPosition =
+            if (isLeftTopTask) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT
+
+        val animationRunner = RemoteRecentSplitTaskTransitionRunner(taskId, task2Id,
+            view.locationOnScreen, view.context.display.naturalBounds, handleResult)
+        val remoteTransition = RemoteTransition(animationRunner,
+            view.context.iApplicationThread, "startSplitScreenTask")
+
+        splitScreen.get().startTasks(taskId, activityOptions.toBundle(), task2Id, null,
+            splitPosition, splitBounds.snapPosition, remoteTransition, null)
+    }
+
+
     override fun onTaskSizeChanged(size: Rect) {
         views?.recentsContainer?.setTaskHeightSize()
     }
@@ -163,12 +220,12 @@
         val thumbnailHeight = taskViewSizeProvider.size.height()
         val itemHeight =
             thumbnailHeight +
-                context.resources.getDimensionPixelSize(
-                    R.dimen.media_projection_app_selector_task_icon_size
-                ) +
-                context.resources.getDimensionPixelSize(
-                    R.dimen.media_projection_app_selector_task_icon_margin
-                ) * 2
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.media_projection_app_selector_task_icon_size
+                    ) +
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.media_projection_app_selector_task_icon_margin
+                    ) * 2
 
         layoutParams = layoutParams.apply { height = itemHeight }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 3fe040a..3b84d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -21,12 +21,12 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import com.android.systemui.res.R
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BadgedAppIconLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -39,7 +39,7 @@
 @AssistedInject
 constructor(
     @Assisted private val root: ViewGroup,
-    private val iconLoader: AppIconLoader,
+    private val iconLoader: BadgedAppIconLoader,
     private val thumbnailLoader: RecentTaskThumbnailLoader,
     private val labelLoader: RecentTaskLabelLoader,
     private val taskViewSizeProvider: TaskPreviewSizeProvider,
@@ -63,7 +63,7 @@
             scope.launch {
                 task.baseIntentComponent?.let { component ->
                     launch {
-                        val icon = iconLoader.loadIcon(task.userId, component)
+                        val icon = iconLoader.loadIcon(task.userId, task.userType, component)
                         iconView.setImageDrawable(icon)
                     }
                     launch {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
new file mode 100644
index 0000000..9514c4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.mediaprojection.appselector.view
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.annotation.UiThread
+import android.graphics.Rect
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import android.view.SurfaceControl
+import android.view.animation.DecelerateInterpolator
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import com.android.app.viewcapture.ViewCapture
+import com.android.internal.policy.TransitionAnimation
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.Companion.TAG
+
+class RemoteRecentSplitTaskTransitionRunner(
+    private val firstTaskId: Int,
+    private val secondTaskId: Int,
+    private val viewPosition: IntArray,
+    private val screenBounds: Rect,
+    private val handleResult: () -> Unit,
+) : IRemoteTransition.Stub() {
+    override fun startAnimation(
+        transition: IBinder?,
+        info: TransitionInfo?,
+        t: SurfaceControl.Transaction?,
+        finishedCallback: IRemoteTransitionFinishedCallback
+    ) {
+        val launchAnimation = AnimatorSet()
+        var rootCandidate =
+            info!!.changes.firstOrNull {
+                it.taskInfo?.taskId == firstTaskId || it.taskInfo?.taskId == secondTaskId
+            }
+
+        // If we could not find a proper root candidate, something went wrong.
+        check(rootCandidate != null) { "Could not find a split root candidate" }
+
+        // Recurse up the tree until parent is null, then we've found our root.
+        var parentToken: WindowContainerToken? = rootCandidate.parent
+        while (parentToken != null) {
+            rootCandidate = info.getChange(parentToken) ?: break
+            parentToken = rootCandidate.parent
+        }
+
+        // Make sure nothing weird happened, like getChange() returning null.
+        check(rootCandidate != null) { "Failed to find a root leash" }
+
+        // Ending position is the full device screen.
+        val startingScale = 0.25f
+
+        val startX = viewPosition[0]
+        val startY = viewPosition[1]
+        val endX = screenBounds.left
+        val endY = screenBounds.top
+
+        ViewCapture.MAIN_EXECUTOR.execute {
+            val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
+            with(progressUpdater) {
+                interpolator = DecelerateInterpolator(1.5f)
+                setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION.toLong())
+
+                addUpdateListener { valueAnimator ->
+                    val progress = valueAnimator.animatedFraction
+
+                    val x = startX + ((endX - startX) * progress)
+                    val y = startY + ((endY - startY) * progress)
+                    val scale = startingScale + ((1 - startingScale) * progress)
+
+                    t!!
+                        .setPosition(rootCandidate.leash, x, y)
+                        .setScale(rootCandidate.leash, scale, scale)
+                        .setAlpha(rootCandidate.leash, progress)
+                        .apply()
+                }
+
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator) {
+                            try {
+                                onTransitionFinished()
+                                finishedCallback.onTransitionFinished(null, null)
+                            } catch (e: RemoteException) {
+                                Log.e(TAG, "Failed to call transition finished callback", e)
+                            }
+                        }
+                    }
+                )
+            }
+
+            launchAnimation.play(progressUpdater)
+            launchAnimation.start()
+        }
+    }
+
+    override fun mergeAnimation(
+        transition: IBinder?,
+        info: TransitionInfo?,
+        t: SurfaceControl.Transaction?,
+        mergeTarget: IBinder?,
+        finishedCallback: IRemoteTransitionFinishedCallback?
+    ) {}
+
+    @Throws(RemoteException::class)
+    override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) {
+        Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted")
+    }
+
+    @UiThread
+    private fun onTransitionFinished() {
+        // After finished transition, then invoke callback to close the app selector, so that
+        // finish animation of app selector does not override the launch animation of the split
+        // tasks
+        handleResult()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index b3d848c..30f33a3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -34,7 +34,6 @@
 import androidx.core.os.postDelayed
 import androidx.core.view.isVisible
 import androidx.dynamicanimation.animation.DynamicAnimation
-import com.android.internal.jank.Cuj.CUJ_BACK_PANEL_ARROW
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
@@ -1039,7 +1038,7 @@
     override fun dump(pw: PrintWriter) {
         pw.println("$TAG:")
         pw.println("  currentState=$currentState")
-        pw.println("  isLeftPanel=$mView.isLeftPanel")
+        pw.println("  isLeftPanel=${mView.isLeftPanel}")
     }
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 3f8834a..9380d44 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -281,5 +281,10 @@
                 powerButtonLaunchGestureTriggeredDuringSleep = false,
             )
         }
+
+        /** Helper method for tests to simulate the device screen state change event. */
+        fun PowerInteractor.setScreenPowerState(screenPowerState: ScreenPowerState) {
+            this.onScreenPowerStateUpdated(screenPowerState)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index 3b3844a..e424975 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -31,7 +31,7 @@
 import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.settings.brightness.MirrorController;
 import com.android.systemui.util.LifecycleFragment;
 
 import java.util.function.Consumer;
@@ -182,7 +182,7 @@
     }
 
     public void setBrightnessMirrorController(
-            BrightnessMirrorController brightnessMirrorController) {
+            MirrorController brightnessMirrorController) {
         if (mQsImpl != null) {
             mQsImpl.setBrightnessMirrorController(brightnessMirrorController);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a000d63..a0607e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -61,6 +61,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.settings.brightness.MirrorController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
@@ -68,7 +69,6 @@
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 import com.android.systemui.util.Utils;
 
@@ -544,7 +544,7 @@
     }
 
     public void setBrightnessMirrorController(
-            BrightnessMirrorController brightnessMirrorController) {
+            @Nullable MirrorController brightnessMirrorController) {
         mQSPanelController.setBrightnessMirror(brightnessMirrorController);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index cd65119..55dc485 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -24,6 +24,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
@@ -38,9 +40,9 @@
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.tuner.TunerService;
 
@@ -139,6 +141,7 @@
         mBrightnessMirrorHandler.onQsPanelAttached();
         PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout());
         pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener);
+        maybeReinflateBrightnessSlider();
     }
 
     @Override
@@ -157,15 +160,18 @@
     @Override
     protected void onConfigurationChanged() {
         mView.updateResources();
+        maybeReinflateBrightnessSlider();
+        if (mView.isListening()) {
+            refreshAllTiles();
+        }
+    }
+
+    private void maybeReinflateBrightnessSlider() {
         int newDensity = mView.getResources().getConfiguration().densityDpi;
         if (newDensity != mLastDensity) {
             mLastDensity = newDensity;
             reinflateBrightnessSlider();
         }
-
-        if (mView.isListening()) {
-            refreshAllTiles();
-        }
     }
 
     private void reinflateBrightnessSlider() {
@@ -210,7 +216,7 @@
         }
     }
 
-    public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+    public void setBrightnessMirror(@Nullable MirrorController brightnessMirrorController) {
         mBrightnessMirrorHandler.setController(brightnessMirrorController);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index b0707db..6eae32a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -39,6 +39,7 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -51,7 +52,6 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 8d5aeab5..3d86e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.QSImpl
 import com.android.systemui.qs.dagger.QSSceneComponent
 import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.MirrorController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.util.kotlin.sample
@@ -68,6 +69,9 @@
      */
     val qsView: Flow<View>
 
+    /** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */
+    fun setBrightnessMirrorController(mirrorController: MirrorController?)
+
     /**
      * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
      * [qsView]. Re-inflations due to configuration changes will use the last used [context].
@@ -206,7 +210,7 @@
         applicationScope.launch {
             launch {
                 state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
-                    _qsImpl.value?.apply {
+                    qsImpl.value?.apply {
                         if (state != QSSceneAdapter.State.QS && customizing) {
                             this@apply.closeCustomizerImmediately()
                         }
@@ -284,6 +288,10 @@
         qsImpl.value?.closeCustomizer()
     }
 
+    override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+        qsImpl.value?.setBrightnessMirrorController(mirrorController)
+    }
+
     private fun QSImpl.applyState(state: QSSceneAdapter.State) {
         setQsVisible(state.isVisible)
         setExpanded(state.isVisible && state.expansion > 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 62ed491..4e0b576 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -25,11 +25,15 @@
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Models UI state and handles user input for the quick settings scene. */
@@ -37,25 +41,29 @@
 class QuickSettingsSceneViewModel
 @Inject
 constructor(
+    val brightnessMirrorViewModel: BrightnessMirrorViewModel,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val qsSceneAdapter: QSSceneAdapter,
     val notifications: NotificationsPlaceholderViewModel,
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
+    private val sceneInteractor: SceneInteractor,
 ) {
     val destinationScenes =
-        qsSceneAdapter.isCustomizing.map { customizing ->
+        qsSceneAdapter.isCustomizing.flatMapLatest { customizing ->
             if (customizing) {
                 // TODO(b/332749288) Empty map so there are no back handlers and back can close
                 // customizer
-                emptyMap()
+                flowOf(emptyMap())
                 // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
                 // while customizing
             } else {
-                mapOf(
-                    Back to UserActionResult(Scenes.Shade),
-                    Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                )
+                sceneInteractor.previousScene.map { previousScene ->
+                    mapOf(
+                        Back to UserActionResult(previousScene ?: Scenes.Shade),
+                        Swipe(SwipeDirection.Up) to UserActionResult(previousScene ?: Scenes.Shade),
+                    )
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 5e4919d..4d34a86 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recordissue
 
+import android.app.IActivityManager
 import android.app.NotificationManager
 import android.content.Context
 import android.content.Intent
@@ -51,7 +52,7 @@
 @Inject
 constructor(
     controller: RecordingController,
-    @LongRunning executor: Executor,
+    @LongRunning private val bgExecutor: Executor,
     @Main handler: Handler,
     uiEventLogger: UiEventLogger,
     notificationManager: NotificationManager,
@@ -60,10 +61,12 @@
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val panelInteractor: PanelInteractor,
     private val issueRecordingState: IssueRecordingState,
+    private val iActivityManager: IActivityManager,
+    private val launcherApps: LauncherApps,
 ) :
     RecordingService(
         controller,
-        executor,
+        bgExecutor,
         handler,
         uiEventLogger,
         notificationManager,
@@ -103,12 +106,26 @@
                 // ViewCapture needs to save it's data before it is disabled, or else the data will
                 // be lost. This is expected to change in the near future, and when that happens
                 // this line should be removed.
-                getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
+                launcherApps.saveViewCaptureData()
                 TraceUtils.traceStop(contentResolver)
                 issueRecordingState.isRecording = false
             }
             ACTION_SHARE -> {
-                shareRecording(intent)
+                bgExecutor.execute {
+                    mNotificationManager.cancelAsUser(
+                        null,
+                        mNotificationId,
+                        UserHandle(mUserContextTracker.userContext.userId)
+                    )
+
+                    val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java)
+                    if (issueRecordingState.takeBugReport) {
+                        iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
+                    } else {
+                        shareRecording(screenRecording)
+                    }
+                }
+
                 dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
                 panelInteractor.collapsePanels()
 
@@ -122,23 +139,17 @@
         return super.onStartCommand(intent, flags, startId)
     }
 
-    private fun shareRecording(intent: Intent) {
+    private fun shareRecording(screenRecording: Uri?) {
         val sharableUri: Uri =
             zipAndPackageRecordings(
                 TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
-                intent.getStringExtra(EXTRA_PATH)
+                screenRecording
             )
                 ?: return
         val sendIntent =
             FileSender.buildSendIntent(this, listOf(sharableUri))
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
-        mNotificationManager.cancelAsUser(
-            null,
-            mNotificationId,
-            UserHandle(mUserContextTracker.userContext.userId)
-        )
-
         // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
         mKeyguardDismissUtil.executeWhenUnlocked(
             {
@@ -150,7 +161,7 @@
         )
     }
 
-    private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+    private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
         try {
             externalCacheDir?.mkdirs()
             val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -160,8 +171,8 @@
                     Files.copy(file.toPath(), os)
                     os.closeEntry()
                 }
-                if (screenRecordingUri != null) {
-                    contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+                if (screenRecording != null) {
+                    contentResolver.openInputStream(screenRecording)?.use {
                         os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
                         it.transferTo(os)
                         os.closeEntry()
@@ -215,7 +226,7 @@
         fun getStartIntent(
             context: Context,
             screenRecord: Boolean,
-            winscopeTracing: Boolean
+            winscopeTracing: Boolean,
         ): Intent =
             Intent(context, IssueRecordingService::class.java)
                 .setAction(ACTION_START)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 394c5c2..12ed06d 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -25,6 +25,8 @@
 
     private val listeners = CopyOnWriteArrayList<Runnable>()
 
+    var takeBugReport: Boolean = false
+
     var isRecording = false
         set(value) {
             field = value
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dab61fa..68b8836 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -63,6 +63,7 @@
     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
     private val userFileManager: UserFileManager,
     private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
+    private val issueRecordingState: IssueRecordingState,
     @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
 ) : SystemUIDialog.Delegate {
 
@@ -74,6 +75,7 @@
     }
 
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+    @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var bugReportSwitch: Switch
     private lateinit var issueTypeButton: Button
 
     @MainThread
@@ -86,6 +88,7 @@
             setPositiveButton(
                 R.string.qs_record_issue_start,
                 { _, _ ->
+                    issueRecordingState.takeBugReport = bugReportSwitch.isChecked
                     onStarted.accept(
                         IssueRecordingConfig(
                             screenRecordSwitch.isChecked,
@@ -113,6 +116,7 @@
                     bgExecutor.execute { onScreenRecordSwitchClicked() }
                 }
             }
+            bugReportSwitch = requireViewById(R.id.bugreport_switch)
             val startButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
             issueTypeButton = requireViewById(R.id.issue_type_button)
             issueTypeButton.setOnClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 994b012..3082eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.util.kotlin.WithPrev
+import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,6 +36,7 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Source of truth for scene framework application state. */
@@ -44,7 +47,32 @@
     private val config: SceneContainerConfig,
     private val dataSource: SceneDataSource,
 ) {
-    val currentScene: StateFlow<SceneKey> = dataSource.currentScene
+    private val previousAndCurrentScene: StateFlow<WithPrev<SceneKey?, SceneKey>> =
+        dataSource.currentScene
+            .pairwise()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = WithPrev(null, dataSource.currentScene.value),
+            )
+
+    val currentScene: StateFlow<SceneKey> =
+        previousAndCurrentScene
+            .map { it.newValue }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = previousAndCurrentScene.value.newValue,
+            )
+
+    val previousScene: StateFlow<SceneKey?> =
+        previousAndCurrentScene
+            .map { it.previousValue }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = previousAndCurrentScene.value.previousValue,
+            )
 
     private val _isVisible = MutableStateFlow(true)
     val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 2ccd3b9..0239455 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -140,6 +140,14 @@
             )
 
     /**
+     * The previous scene.
+     *
+     * This is effectively the previous value of [currentScene] which means that all caveats, for
+     * example regarding when in a transition the current scene changes, apply.
+     */
+    val previousScene: StateFlow<SceneKey?> = repository.previousScene
+
+    /**
      * Returns the keys of all scenes in the container.
      *
      * The scenes will be sorted in z-order such that the last one is the one that should be
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b2c01e1..cbb61b3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -206,7 +206,7 @@
                 break;
 
             case ACTION_SHARE:
-                Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
+                Uri shareUri = intent.getParcelableExtra(EXTRA_PATH, Uri.class);
 
                 Intent shareIntent = new Intent(Intent.ACTION_SEND)
                         .setType("video/mp4")
@@ -356,7 +356,7 @@
                 PendingIntent.getService(
                         this,
                         REQUEST_CODE,
-                        getShareIntent(this, uri != null ? uri.toString() : null),
+                        getShareIntent(this, uri),
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .build();
 
@@ -512,7 +512,7 @@
         return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
     }
 
-    private Intent getShareIntent(Context context, String path) {
+    private Intent getShareIntent(Context context, Uri path) {
         return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
                 .putExtra(EXTRA_PATH, path);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
new file mode 100644
index 0000000..caa67df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.ExitTransitionCoordinator
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.UserHandle
+import android.util.Log
+import android.util.Pair
+import android.view.View
+import android.view.Window
+import com.android.app.tracing.coroutines.launch
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+
+class ActionExecutor
+@AssistedInject
+constructor(
+    private val intentExecutor: ActionIntentExecutor,
+    @Application private val applicationScope: CoroutineScope,
+    @Assisted val window: Window,
+    @Assisted val transitionView: View,
+    @Assisted val onDismiss: (() -> Unit)
+) {
+
+    var isPendingSharedTransition = false
+        private set
+
+    fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
+        isPendingSharedTransition = true
+        val windowTransition = createWindowTransition()
+        applicationScope.launch("$TAG#launchIntentAsync") {
+            intentExecutor.launchIntent(
+                intent,
+                user,
+                overrideTransition,
+                windowTransition.first,
+                windowTransition.second
+            )
+        }
+    }
+
+    fun sendPendingIntent(pendingIntent: PendingIntent) {
+        try {
+            val options = BroadcastOptions.makeBasic()
+            options.setInteractive(true)
+            options.setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+            )
+            pendingIntent.send(options.toBundle())
+            onDismiss.invoke()
+        } catch (e: PendingIntent.CanceledException) {
+            Log.e(TAG, "Intent cancelled", e)
+        }
+    }
+
+    /**
+     * Supplies the necessary bits for the shared element transition to share sheet. Note that once
+     * called, the action intent to share must be sent immediately after.
+     */
+    private fun createWindowTransition(): Pair<ActivityOptions, ExitTransitionCoordinator> {
+        val callbacks: ExitTransitionCallbacks =
+            object : ExitTransitionCallbacks {
+                override fun isReturnTransitionAllowed(): Boolean {
+                    return false
+                }
+
+                override fun hideSharedElements() {
+                    isPendingSharedTransition = false
+                    onDismiss.invoke()
+                }
+
+                override fun onFinish() {}
+            }
+        return ActivityOptions.startSharedElementAnimation(
+            window,
+            callbacks,
+            null,
+            Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+    }
+
+    companion object {
+        private const val TAG = "ActionExecutor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 8e9769ab..a0cef52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,7 +23,9 @@
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
+import android.os.UserHandle
 import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity
 
 object ActionIntentCreator {
     /** @return a chooser intent to share the given URI. */
@@ -89,6 +91,14 @@
             .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
     }
 
+    /** @return an Intent to start the LongScreenshotActivity */
+    fun createLongScreenshotIntent(owner: UserHandle, context: Context): Intent {
+        return Intent(context, LongScreenshotActivity::class.java)
+            .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
+            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+    }
+
     private const val EXTRA_EDIT_SOURCE = "edit_source"
     private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1f9853b..4eca51d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -25,7 +25,6 @@
 import android.os.RemoteException
 import android.os.UserHandle
 import android.util.Log
-import android.util.Pair
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.IRemoteAnimationRunner
 import android.view.RemoteAnimationAdapter
@@ -67,20 +66,22 @@
      */
     fun launchIntentAsync(
         intent: Intent,
-        transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
         user: UserHandle,
         overrideTransition: Boolean,
+        options: ActivityOptions?,
+        transitionCoordinator: ExitTransitionCoordinator?,
     ) {
         applicationScope.launch("$TAG#launchIntentAsync") {
-            launchIntent(intent, transition, user, overrideTransition)
+            launchIntent(intent, user, overrideTransition, options, transitionCoordinator)
         }
     }
 
     suspend fun launchIntent(
         intent: Intent,
-        transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
         user: UserHandle,
         overrideTransition: Boolean,
+        options: ActivityOptions?,
+        transitionCoordinator: ExitTransitionCoordinator?,
     ) {
         if (screenshotActionDismissSystemWindows()) {
             keyguardController.dismiss()
@@ -90,14 +91,12 @@
         } else {
             dismissKeyguard()
         }
-        transition?.second?.startExit()
+        transitionCoordinator?.startExit()
 
         if (user == myUserHandle()) {
-            withContext(mainDispatcher) {
-                context.startActivity(intent, transition?.first?.toBundle())
-            }
+            withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) }
         } else {
-            launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
+            launchCrossProfileIntent(user, intent, options?.toBundle())
         }
 
         if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 19c0b16..0ccb19c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -16,48 +16,32 @@
 
 package com.android.systemui.screenshot
 
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
-import android.app.ExitTransitionCoordinator
-import android.app.PendingIntent
 import android.app.assist.AssistContent
 import android.content.Context
-import android.content.Intent
-import android.os.Process
-import android.os.UserHandle
-import android.provider.DeviceConfig
 import android.util.Log
-import android.util.Pair
 import androidx.appcompat.content.res.AppCompatResources
-import com.android.app.tracing.coroutines.launch
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.res.R
 import com.android.systemui.screenshot.ActionIntentCreator.createEdit
 import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED
-import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance
 import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import java.text.DateFormat
-import java.util.Date
-import kotlinx.coroutines.CoroutineScope
 
 /**
  * Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
  * implementation.
  */
 interface ScreenshotActionsProvider {
-    fun setCompletedScreenshot(result: SavedImageData)
-    fun isPendingSharedTransition(): Boolean
+    fun onScrollChipReady(onClick: Runnable)
+    fun setCompletedScreenshot(result: ScreenshotSavedResult)
 
     fun onAssistContentAvailable(assistContent: AssistContent) {}
 
@@ -65,8 +49,7 @@
         fun create(
             request: ScreenshotData,
             requestId: String,
-            windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
-            requestDismissal: () -> Unit,
+            actionExecutor: ActionExecutor,
         ): ScreenshotActionsProvider
     }
 }
@@ -76,182 +59,145 @@
 constructor(
     private val context: Context,
     private val viewModel: ScreenshotViewModel,
-    private val actionExecutor: ActionIntentExecutor,
     private val smartActionsProvider: SmartActionsProvider,
     private val uiEventLogger: UiEventLogger,
-    @Application private val applicationScope: CoroutineScope,
     @Assisted val request: ScreenshotData,
     @Assisted val requestId: String,
-    @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
-    @Assisted val requestDismissal: () -> Unit,
+    @Assisted val actionExecutor: ActionExecutor,
 ) : ScreenshotActionsProvider {
-    private var pendingAction: ((SavedImageData) -> Unit)? = null
-    private var result: SavedImageData? = null
-    private var isPendingSharedTransition = false
+    private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+    private var result: ScreenshotSavedResult? = null
 
     init {
         viewModel.setPreviewAction {
             debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
             uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
             onDeferrableActionTapped { result ->
-                startSharedTransition(createEdit(result.uri, context), true)
+                actionExecutor.startSharedTransition(
+                    createEdit(result.uri, context),
+                    result.user,
+                    true
+                )
             }
         }
         viewModel.addAction(
-            ActionButtonViewModel(
+            ActionButtonAppearance(
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
                 context.resources.getString(R.string.screenshot_edit_label),
                 context.resources.getString(R.string.screenshot_edit_description),
-            ) {
-                debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
-                uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
-                onDeferrableActionTapped { result ->
-                    startSharedTransition(createEdit(result.uri, context), true)
-                }
+            )
+        ) {
+            debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+            uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
+            onDeferrableActionTapped { result ->
+                actionExecutor.startSharedTransition(
+                    createEdit(result.uri, context),
+                    result.user,
+                    true
+                )
             }
-        )
+        }
+
         viewModel.addAction(
-            ActionButtonViewModel(
+            ActionButtonAppearance(
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
                 context.resources.getString(R.string.screenshot_share_label),
                 context.resources.getString(R.string.screenshot_share_description),
-            ) {
-                debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
-                uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
-                onDeferrableActionTapped { result ->
-                    startSharedTransition(createShareWithSubject(result.uri, result.subject), false)
-                }
+            )
+        ) {
+            debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
+            uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
+            onDeferrableActionTapped { result ->
+                actionExecutor.startSharedTransition(
+                    createShareWithSubject(result.uri, result.subject),
+                    result.user,
+                    false
+                )
             }
-        )
-        if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) {
-            smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
-                if (!quickShare.actionIntent.isImmutable) {
-                    viewModel.addAction(
-                        ActionButtonViewModel(
-                            quickShare.getIcon().loadDrawable(context),
-                            quickShare.title,
-                            quickShare.title
-                        ) {
-                            debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
-                            onDeferrableActionTapped { result ->
-                                uiEventLogger.log(
-                                    SCREENSHOT_SMART_ACTION_TAPPED,
-                                    0,
-                                    request.packageNameString
-                                )
-                                sendPendingIntent(
-                                    smartActionsProvider
-                                        .wrapIntent(
-                                            quickShare,
-                                            result.uri,
-                                            result.subject,
-                                            requestId
-                                        )
-                                        .actionIntent
-                                )
-                            }
-                        }
+        }
+
+        smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
+            if (!quickShare.actionIntent.isImmutable) {
+                viewModel.addAction(
+                    ActionButtonAppearance(
+                        quickShare.getIcon().loadDrawable(context),
+                        quickShare.title,
+                        quickShare.title
                     )
-                } else {
-                    Log.w(TAG, "Received immutable quick share pending intent; ignoring")
+                ) {
+                    debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
+                    onDeferrableActionTapped { result ->
+                        uiEventLogger.log(
+                            SCREENSHOT_SMART_ACTION_TAPPED,
+                            0,
+                            request.packageNameString
+                        )
+                        val pendingIntentWithUri =
+                            smartActionsProvider.wrapIntent(
+                                quickShare,
+                                result.uri,
+                                result.subject,
+                                requestId
+                            )
+                        actionExecutor.sendPendingIntent(pendingIntentWithUri)
+                    }
                 }
+            } else {
+                Log.w(TAG, "Received immutable quick share pending intent; ignoring")
             }
         }
     }
 
-    override fun setCompletedScreenshot(result: SavedImageData) {
+    override fun onScrollChipReady(onClick: Runnable) {
+        viewModel.addAction(
+            ActionButtonAppearance(
+                AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
+                context.resources.getString(R.string.screenshot_scroll_label),
+                context.resources.getString(R.string.screenshot_scroll_label),
+            )
+        ) {
+            onClick.run()
+        }
+    }
+
+    override fun setCompletedScreenshot(result: ScreenshotSavedResult) {
         if (this.result != null) {
             Log.e(TAG, "Got a second completed screenshot for existing request!")
             return
         }
-        if (result.uri == null || result.owner == null || result.imageTime == null) {
-            Log.e(TAG, "Invalid result provided!")
-            return
-        }
-        if (result.subject == null) {
-            result.subject = getSubjectString(result.imageTime)
-        }
         this.result = result
         pendingAction?.invoke(result)
-        if (smartActionsEnabled(result.owner)) {
-            smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
-                viewModel.addActions(
-                    smartActions.map {
-                        ActionButtonViewModel(
-                            it.getIcon().loadDrawable(context),
-                            it.title,
-                            it.title
-                        ) {
-                            sendPendingIntent(it.actionIntent)
-                        }
+        smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
+            smartActions.forEach {
+                smartActions.forEach { action ->
+                    viewModel.addAction(
+                        ActionButtonAppearance(
+                            action.getIcon().loadDrawable(context),
+                            action.title,
+                            action.title,
+                        )
+                    ) {
+                        actionExecutor.sendPendingIntent(action.actionIntent)
                     }
-                )
+                }
             }
         }
     }
 
-    override fun isPendingSharedTransition(): Boolean {
-        return isPendingSharedTransition
-    }
-
-    private fun onDeferrableActionTapped(onResult: (SavedImageData) -> Unit) {
+    private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
         result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
     }
 
-    private fun startSharedTransition(intent: Intent, overrideTransition: Boolean) {
-        val user =
-            result?.owner
-                ?: run {
-                    Log.wtf(TAG, "User handle not provided in screenshot result! Result: $result")
-                    return
-                }
-        isPendingSharedTransition = true
-        applicationScope.launch("$TAG#launchIntentAsync") {
-            actionExecutor.launchIntent(intent, windowTransition.invoke(), user, overrideTransition)
-        }
-    }
-
-    private fun sendPendingIntent(pendingIntent: PendingIntent) {
-        try {
-            val options = BroadcastOptions.makeBasic()
-            options.setInteractive(true)
-            options.setPendingIntentBackgroundActivityStartMode(
-                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-            )
-            pendingIntent.send(options.toBundle())
-            requestDismissal.invoke()
-        } catch (e: PendingIntent.CanceledException) {
-            Log.e(TAG, "Intent cancelled", e)
-        }
-    }
-
-    private fun smartActionsEnabled(user: UserHandle): Boolean {
-        val savingToOtherUser = user != Process.myUserHandle()
-        return !savingToOtherUser &&
-            DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
-                true
-            )
-    }
-
-    private fun getSubjectString(imageTime: Long): String {
-        val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
-        return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
-    }
-
     @AssistedFactory
     interface Factory : ScreenshotActionsProvider.Factory {
         override fun create(
             request: ScreenshotData,
             requestId: String,
-            windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
-            requestDismissal: () -> Unit,
+            actionExecutor: ActionExecutor,
         ): DefaultScreenshotActionsProvider
     }
 
     companion object {
         private const val TAG = "ScreenshotActionsProvider"
-        private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1e513b2..13dd229 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,7 +34,6 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ExitTransitionCoordinator;
 import android.app.ICompatCameraControlCallback;
@@ -51,7 +50,6 @@
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -59,17 +57,12 @@
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
 import android.view.ScrollCaptureResponse;
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.widget.Toast;
 import android.window.WindowContext;
 
@@ -84,10 +77,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.res.R;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
-import com.android.systemui.screenshot.scroll.LongScreenshotData;
-import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
 import com.android.systemui.util.Assert;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -100,12 +90,9 @@
 
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 import java.util.function.Consumer;
 
 import javax.inject.Provider;
@@ -117,34 +104,6 @@
 public class ScreenshotController {
     private static final String TAG = logTag(ScreenshotController.class);
 
-    private ScrollCaptureResponse mLastScrollCaptureResponse;
-    private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
-
-    /**
-     * This is effectively a no-op, but we need something non-null to pass in, in order to
-     * successfully override the pending activity entrance animation.
-     */
-    static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
-            new IRemoteAnimationRunner.Stub() {
-                @Override
-                public void onAnimationStart(
-                        @WindowManager.TransitionOldType int transit,
-                        RemoteAnimationTarget[] apps,
-                        RemoteAnimationTarget[] wallpapers,
-                        RemoteAnimationTarget[] nonApps,
-                        final IRemoteAnimationFinishedCallback finishedCallback) {
-                    try {
-                        finishedCallback.onAnimationFinished();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error finishing screenshot remote animation", e);
-                    }
-                }
-
-                @Override
-                public void onAnimationCancelled() {
-                }
-            };
-
     /**
      * POD used in the AsyncTask which saves an image in the background.
      */
@@ -242,22 +201,20 @@
     private final ExecutorService mBgExecutor;
     private final BroadcastSender mBroadcastSender;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final ActionExecutor mActionExecutor;
 
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
     @Nullable
     private final ScreenshotSoundController mScreenshotSoundController;
-    private final ScrollCaptureClient mScrollCaptureClient;
     private final PhoneWindow mWindow;
     private final DisplayManager mDisplayManager;
     private final int mDisplayId;
-    private final ScrollCaptureController mScrollCaptureController;
-    private final LongScreenshotData mLongScreenshotHolder;
-    private final boolean mIsLowRamDevice;
+    private final ScrollCaptureExecutor mScrollCaptureExecutor;
     private final ScreenshotNotificationSmartActionsProvider
             mScreenshotNotificationSmartActionsProvider;
     private final TimeoutHandler mScreenshotHandler;
-    private final ActionIntentExecutor mActionExecutor;
+    private final ActionIntentExecutor mActionIntentExecutor;
     private final UserManager mUserManager;
     private final AssistContentRequester mAssistContentRequester;
 
@@ -299,19 +256,17 @@
             ScreenshotActionsProvider.Factory actionsProviderFactory,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
-            ScrollCaptureClient scrollCaptureClient,
             UiEventLogger uiEventLogger,
             ImageExporter imageExporter,
             ImageCapture imageCapture,
             @Main Executor mainExecutor,
-            ScrollCaptureController scrollCaptureController,
-            LongScreenshotData longScreenshotHolder,
-            ActivityManager activityManager,
+            ScrollCaptureExecutor scrollCaptureExecutor,
             TimeoutHandler timeoutHandler,
             BroadcastSender broadcastSender,
             BroadcastDispatcher broadcastDispatcher,
             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
-            ActionIntentExecutor actionExecutor,
+            ActionIntentExecutor actionIntentExecutor,
+            ActionExecutor.Factory actionExecutorFactory,
             UserManager userManager,
             AssistContentRequester assistContentRequester,
             MessageContainerController messageContainerController,
@@ -322,14 +277,11 @@
         mScreenshotSmartActions = screenshotSmartActions;
         mActionsProviderFactory = actionsProviderFactory;
         mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
-        mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
         mImageCapture = imageCapture;
         mMainExecutor = mainExecutor;
-        mScrollCaptureController = scrollCaptureController;
-        mLongScreenshotHolder = longScreenshotHolder;
-        mIsLowRamDevice = activityManager.isLowRamDevice();
+        mScrollCaptureExecutor = scrollCaptureExecutor;
         mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
         mBgExecutor = Executors.newSingleThreadExecutor();
         mBroadcastSender = broadcastSender;
@@ -345,7 +297,7 @@
         final Context displayContext = context.createDisplayContext(getDisplay());
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mFlags = flags;
-        mActionExecutor = actionExecutor;
+        mActionIntentExecutor = actionIntentExecutor;
         mUserManager = userManager;
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
@@ -369,6 +321,12 @@
         mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
 
+        mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+                () -> {
+                    requestDismissal(null);
+                    return Unit.INSTANCE;
+                });
+
         // Sound is only reproduced from the controller of the default display.
         if (displayId == Display.DEFAULT_DISPLAY) {
             mScreenshotSoundController = screenshotSoundController.get();
@@ -395,10 +353,10 @@
         Assert.isMainThread();
 
         mCurrentRequestCallback = requestCallback;
-        if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+        if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+                && screenshot.getBitmap() == null) {
             Rect bounds = getFullScreenRect();
-            screenshot.setBitmap(
-                    mImageCapture.captureDisplay(mDisplayId, bounds));
+            screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds));
             screenshot.setScreenBounds(bounds);
         }
 
@@ -447,11 +405,8 @@
         if (screenshotShelfUi()) {
             final UUID requestId = UUID.randomUUID();
             final String screenshotId = String.format("Screenshot_%s", requestId);
-            mActionsProvider = mActionsProviderFactory.create(screenshot, screenshotId,
-                    this::createWindowTransition, () -> {
-                        mViewProxy.requestDismissal(null);
-                        return Unit.INSTANCE;
-                    });
+            mActionsProvider = mActionsProviderFactory.create(
+                    screenshot, screenshotId, mActionExecutor);
             saveScreenshotInBackground(screenshot, requestId, finisher);
 
             if (screenshot.getTaskId() >= 0) {
@@ -548,7 +503,7 @@
 
     boolean isPendingSharedTransition() {
         if (screenshotShelfUi()) {
-            return mActionsProvider != null && mActionsProvider.isPendingSharedTransition();
+            return mActionExecutor.isPendingSharedTransition();
         } else {
             return mViewProxy.isPendingSharedTransition();
         }
@@ -599,8 +554,9 @@
 
             @Override
             public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
-                mActionExecutor.launchIntentAsync(
-                        intent, createWindowTransition(), owner, overrideTransition);
+                Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
+                mActionIntentExecutor.launchIntentAsync(
+                        intent, owner, overrideTransition, exit.first, exit.second);
             }
 
             @Override
@@ -637,9 +593,8 @@
                                 mViewProxy.hideScrollChip();
                                 // Delay scroll capture eval a bit to allow the underlying activity
                                 // to set up in the new orientation.
-                                mScreenshotHandler.postDelayed(() -> {
-                                    requestScrollCapture(owner);
-                                }, 150);
+                                mScreenshotHandler.postDelayed(
+                                        () -> requestScrollCapture(owner), 150);
                                 mViewProxy.updateInsets(
                                         mWindowManager.getCurrentWindowMetrics().getWindowInsets());
                                 // Screenshot animation calculations won't be valid anymore,
@@ -662,119 +617,51 @@
     }
 
     private void requestScrollCapture(UserHandle owner) {
-        if (!allowLongScreenshots()) {
-            Log.d(TAG, "Long screenshots not supported on this device");
+        mScrollCaptureExecutor.requestScrollCapture(
+                mDisplayId,
+                mWindow.getDecorView().getWindowToken(),
+                (response) -> {
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+                            0, response.getPackageName());
+                    if (screenshotShelfUi() && mActionsProvider != null) {
+                        mActionsProvider.onScrollChipReady(
+                                () -> onScrollButtonClicked(owner, response));
+                    } else {
+                        mViewProxy.showScrollChip(response.getPackageName(),
+                                () -> onScrollButtonClicked(owner, response));
+                    }
+                    return Unit.INSTANCE;
+                }
+        );
+    }
+
+    private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+        if (DEBUG_INPUT) {
+            Log.d(TAG, "scroll chip tapped");
+        }
+        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+                response.getPackageName());
+        Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
+        if (newScreenshot == null) {
+            Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
             return;
         }
-        mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
-        if (mLastScrollCaptureRequest != null) {
-            mLastScrollCaptureRequest.cancel(true);
-        }
-        final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(
-                mDisplayId);
-        mLastScrollCaptureRequest = future;
-        mLastScrollCaptureRequest.addListener(() ->
-                onScrollCaptureResponseReady(future, owner), mMainExecutor);
+        // delay starting scroll capture to make sure scrim is up before the app moves
+        mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+                mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
     }
 
-    private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
-            UserHandle owner) {
-        try {
-            if (mLastScrollCaptureResponse != null) {
-                mLastScrollCaptureResponse.close();
-                mLastScrollCaptureResponse = null;
-            }
-            if (responseFuture.isCancelled()) {
-                return;
-            }
-            mLastScrollCaptureResponse = responseFuture.get();
-            if (!mLastScrollCaptureResponse.isConnected()) {
-                // No connection means that the target window wasn't found
-                // or that it cannot support scroll capture.
-                Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
-                        + mLastScrollCaptureResponse.getWindowTitle() + "]");
-                return;
-            }
-            Log.d(TAG, "ScrollCapture: connected to window ["
-                    + mLastScrollCaptureResponse.getWindowTitle() + "]");
+    private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+        mScrollCaptureExecutor.executeBatchScrollCapture(response,
+                () -> {
+                    final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+                            owner, mContext);
+                    mActionIntentExecutor.launchIntentAsync(intent, owner, true,
+                            ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
 
-            final ScrollCaptureResponse response = mLastScrollCaptureResponse;
-            mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
-                Bitmap newScreenshot =
-                        mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
-
-                if (newScreenshot != null) {
-                    // delay starting scroll capture to make sure scrim is up before the app
-                    // moves
-                    mViewProxy.prepareScrollingTransition(
-                            response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
-                            () -> runBatchScrollCapture(response, owner));
-                } else {
-                    Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
-                }
-            });
-        } catch (InterruptedException | ExecutionException e) {
-            Log.e(TAG, "requestScrollCapture failed", e);
-        }
-    }
-
-    ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
-
-    private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
-        // Clear the reference to prevent close() in dismissScreenshot
-        mLastScrollCaptureResponse = null;
-
-        if (mLongScreenshotFuture != null) {
-            mLongScreenshotFuture.cancel(true);
-        }
-        mLongScreenshotFuture = mScrollCaptureController.run(response);
-        mLongScreenshotFuture.addListener(() -> {
-            ScrollCaptureController.LongScreenshot longScreenshot;
-            try {
-                longScreenshot = mLongScreenshotFuture.get();
-            } catch (CancellationException e) {
-                Log.e(TAG, "Long screenshot cancelled");
-                return;
-            } catch (InterruptedException | ExecutionException e) {
-                Log.e(TAG, "Exception", e);
-                mViewProxy.restoreNonScrollingUi();
-                return;
-            }
-
-            if (longScreenshot.getHeight() == 0) {
-                mViewProxy.restoreNonScrollingUi();
-                return;
-            }
-
-            mLongScreenshotHolder.setLongScreenshot(longScreenshot);
-            mLongScreenshotHolder.setTransitionDestinationCallback(
-                    (transitionDestination, onTransitionEnd) -> {
-                        mViewProxy.startLongScreenshotTransition(
-                                transitionDestination, onTransitionEnd,
-                                longScreenshot);
-                        // TODO: Do this via ActionIntentExecutor instead.
-                        mContext.closeSystemDialogs();
-                    }
-            );
-
-            final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
-            intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
-                    owner);
-            intent.setFlags(
-                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
-            mContext.startActivity(intent,
-                    ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
-            RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
-                    SCREENSHOT_REMOTE_RUNNER, 0, 0);
-            try {
-                WindowManagerGlobal.getWindowManagerService()
-                        .overridePendingAppTransitionRemote(runner,
-                                mDisplayId);
-            } catch (Exception e) {
-                Log.e(TAG, "Error overriding screenshot app transition", e);
-            }
-        }, mMainExecutor);
+                },
+                mViewProxy::restoreNonScrollingUi,
+                mViewProxy::startLongScreenshotTransition);
     }
 
     private void withWindowAttached(Runnable action) {
@@ -919,17 +806,7 @@
     /** Reset screenshot view and then call onCompleteRunnable */
     private void finishDismiss() {
         Log.d(TAG, "finishDismiss");
-        if (mLastScrollCaptureRequest != null) {
-            mLastScrollCaptureRequest.cancel(true);
-            mLastScrollCaptureRequest = null;
-        }
-        if (mLastScrollCaptureResponse != null) {
-            mLastScrollCaptureResponse.close();
-            mLastScrollCaptureResponse = null;
-        }
-        if (mLongScreenshotFuture != null) {
-            mLongScreenshotFuture.cancel(true);
-        }
+        mScrollCaptureExecutor.close();
         if (mCurrentRequestCallback != null) {
             mCurrentRequestCallback.onFinish();
             mCurrentRequestCallback = null;
@@ -942,7 +819,7 @@
     private void saveScreenshotInBackground(
             ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
         ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
-                requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId);
+                requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId);
         future.addListener(() -> {
             try {
                 ImageExporter.Result result = future.get();
@@ -950,12 +827,8 @@
                 logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
                 mScreenshotHandler.resetTimeout();
                 if (result.uri != null) {
-                    final SavedImageData savedImageData = new SavedImageData();
-                    savedImageData.uri = result.uri;
-                    savedImageData.owner = screenshot.getUserHandle();
-                    savedImageData.imageTime = result.timestamp;
-                    mActionsProvider.setCompletedScreenshot(savedImageData);
-                    mViewProxy.setChipIntents(savedImageData);
+                    mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult(
+                            result.uri, screenshot.getUserOrDefault(), result.timestamp));
                 }
                 if (DEBUG_CALLBACK) {
                     Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
@@ -1117,10 +990,6 @@
         return mDisplayManager.getDisplay(mDisplayId);
     }
 
-    private boolean allowLongScreenshots() {
-        return !mIsLowRamDevice;
-    }
-
     private Rect getFullScreenRect() {
         DisplayMetrics displayMetrics = new DisplayMetrics();
         getDisplay().getRealMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index 92e933a..4fdd90b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -5,6 +5,7 @@
 import android.graphics.Insets
 import android.graphics.Rect
 import android.net.Uri
+import android.os.Process
 import android.os.UserHandle
 import android.view.Display
 import android.view.WindowManager.ScreenshotSource
@@ -31,6 +32,10 @@
     val packageNameString: String
         get() = if (topComponent == null) "" else topComponent!!.packageName
 
+    fun getUserOrDefault(): UserHandle {
+        return userHandle ?: Process.myUserHandle()
+    }
+
     companion object {
         @JvmStatic
         fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
index 796457d..3ad4075a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.screenshot
 
 /** Processes a screenshot request sent from [ScreenshotHelper]. */
-interface ScreenshotRequestProcessor {
+fun interface ScreenshotRequestProcessor {
     /**
      * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
      *
-     * @param screenshot the screenshot to process
+     * @param original the screenshot to process
+     * @return a potentially modified screenshot data
      */
-    suspend fun process(screenshot: ScreenshotData): ScreenshotData
+    suspend fun process(original: ScreenshotData): ScreenshotData
 }
 
 /** Exception thrown by [RequestProcessor] if something goes wrong. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
new file mode 100644
index 0000000..5b6e7ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot
+
+import android.net.Uri
+import android.os.UserHandle
+import java.text.DateFormat
+import java.util.Date
+
+/**
+ * Represents a saved screenshot, with the uri and user it was saved to as well as the time it was
+ * saved.
+ */
+data class ScreenshotSavedResult(val uri: Uri, val user: UserHandle, val imageTime: Long) {
+    val subject: String
+
+    init {
+        val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
+        subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
+    }
+
+    companion object {
+        private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 65e8457..59e38a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -259,16 +259,8 @@
         if (DEBUG_SCROLL) {
             Log.d(TAG, "Showing Scroll option");
         }
-        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
         mScrollChip.setVisibility(VISIBLE);
-        mScrollChip.setOnClickListener((v) -> {
-            if (DEBUG_INPUT) {
-                Log.d(TAG, "scroll chip tapped");
-            }
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
-                    packageName);
-            onClick.run();
-        });
+        mScrollChip.setOnClickListener((v) -> onClick.run());
     }
 
     @Override // ViewTreeObserver.OnComputeInternalInsetsListener
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
index 2eaff86..a895b30 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
@@ -65,10 +65,9 @@
         onAction: (Notification.Action) -> Unit
     ) {
         val bitmap = data.bitmap ?: return
-        val user = data.userHandle ?: return
         val component = data.topComponent ?: ComponentName("", "")
-        requestQuickShareAction(id, bitmap, component, user) { quickShareAction ->
-            onAction(quickShareAction)
+        requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare ->
+            onAction(quickShare)
         }
     }
 
@@ -83,14 +82,19 @@
     fun requestSmartActions(
         data: ScreenshotData,
         id: String,
-        result: ScreenshotController.SavedImageData,
+        result: ScreenshotSavedResult,
         onActions: (List<Notification.Action>) -> Unit
     ) {
         val bitmap = data.bitmap ?: return
-        val user = data.userHandle ?: return
-        val uri = result.uri ?: return
         val component = data.topComponent ?: ComponentName("", "")
-        requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions ->
+        requestSmartActions(
+            id,
+            bitmap,
+            component,
+            data.getUserOrDefault(),
+            result.uri,
+            REGULAR_SMART_ACTIONS
+        ) { actions ->
             onActions(actions)
         }
     }
@@ -102,14 +106,14 @@
      * @param uri the URI of the saved screenshot
      * @param subject the subject/title for the screenshot
      * @param id the request ID of the screenshot
-     * @return the wrapped action
+     * @return the pending intent with correct URI
      */
     fun wrapIntent(
         quickShare: Notification.Action,
         uri: Uri,
         subject: String,
         id: String
-    ): Notification.Action {
+    ): PendingIntent {
         val wrappedIntent: Intent =
             Intent(context, SmartActionsReceiver::class.java)
                 .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
@@ -130,17 +134,12 @@
             .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
             .putExtra(ScreenshotController.EXTRA_ID, id)
             .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true)
-        val broadcastIntent =
-            PendingIntent.getBroadcast(
-                context,
-                Random.nextInt(),
-                wrappedIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
-            )
-        return Notification.Action.Builder(quickShare.getIcon(), quickShare.title, broadcastIntent)
-            .setContextual(true)
-            .addExtras(extras)
-            .build()
+        return PendingIntent.getBroadcast(
+            context,
+            Random.nextInt(),
+            wrappedIntent,
+            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+        )
     }
 
     private fun createFillInIntent(uri: Uri, subject: String): Intent {
@@ -197,7 +196,7 @@
             onActions(listOf())
             return
         }
-        var smartActionsFuture: CompletableFuture<List<Notification.Action>>
+        val smartActionsFuture: CompletableFuture<List<Notification.Action>>
         val startTimeMs = SystemClock.uptimeMillis()
         try {
             smartActionsFuture =
@@ -266,6 +265,7 @@
             Log.e(TAG, "Error in notifyScreenshotOp: ", e)
         }
     }
+
     private fun isSmartActionsEnabled(user: UserHandle): Boolean {
         // Smart actions don't yet work for cross-user saves.
         val savingToOtherUser = user !== Process.myUserHandle()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 92d3e55..ec7707c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -92,14 +92,14 @@
         // Let's wait before logging "screenshot requested", as we should log the processed
         // ScreenshotData.
         val screenshotData =
-            try {
-                screenshotRequestProcessor.process(rawScreenshotData)
-            } catch (e: RequestProcessorException) {
-                Log.e(TAG, "Failed to process screenshot request!", e)
-                logScreenshotRequested(rawScreenshotData)
-                onFailedScreenshotRequest(rawScreenshotData, callback)
-                return
-            }
+            runCatching { screenshotRequestProcessor.process(rawScreenshotData) }
+                .onFailure {
+                    Log.e(TAG, "Failed to process screenshot request!", it)
+                    logScreenshotRequested(rawScreenshotData)
+                    onFailedScreenshotRequest(rawScreenshotData, callback)
+                }
+                .getOrNull()
+                ?: return
 
         logScreenshotRequested(screenshotData)
         Log.d(TAG, "Screenshot request: $screenshotData")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
new file mode 100644
index 0000000..c380db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.data.model
+
+import android.content.ComponentName
+import android.graphics.Rect
+
+/** A child task within a RootTaskInfo */
+data class ChildTaskModel(
+    /** The task identifier */
+    val id: Int,
+    /** The task name */
+    val name: String,
+    /** The location and size of the task */
+    val bounds: Rect,
+    /** The user which created the task. */
+    val userId: Int,
+) {
+    val componentName: ComponentName?
+        get() = ComponentName.unflattenFromString(name)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 837a661..2048b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -24,6 +24,6 @@
     val displayId: Int,
     /** Information about the current System UI state which can affect capture. */
     val systemUiState: SystemUiState,
-    /** A list of root tasks on the display, ordered from bottom to top along the z-axis */
+    /** A list of root tasks on the display, ordered from top to bottom along the z-axis */
     val rootTasks: List<RootTaskInfo>,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
index 9c81b32..48e813d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
@@ -18,7 +18,7 @@
 import com.android.systemui.screenshot.data.model.DisplayContentModel
 
 /** Provides information about tasks related to a display. */
-interface DisplayContentRepository {
+fun interface DisplayContentRepository {
     /** Provides information about the tasks and content presented on a given display. */
     suspend fun getDisplayContent(displayId: Int): DisplayContentModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
new file mode 100644
index 0000000..5e2b576
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class CaptureParameters(
+    /** How should the content be captured? */
+    val type: CaptureType,
+    /** The focused or top component at the time of the screenshot. */
+    val component: ComponentName?,
+    /** Which user should receive the screenshot file? */
+    val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
new file mode 100644
index 0000000..4a88180
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.policy
+
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+
+/** Contains logic to determine when and how an adjust to screenshot behavior applies. */
+fun interface CapturePolicy {
+    /**
+     * Test the policy against the current display task state. If the policy applies, Returns a
+     * non-null [CaptureParameters] describing how the screenshot request should be augmented.
+     */
+    suspend fun apply(content: DisplayContentModel): CaptureParameters?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
index 5d2b0ad2..6ca2e9d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -14,7 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.systemui.screenshot.policy
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+import android.graphics.Rect
+
+/** What to capture */
+sealed interface CaptureType {
+    /** Capture the entire screen contents. */
+    class FullScreen(val displayId: Int) : CaptureType
+
+    /** Capture the contents of the task only. */
+    class IsolatedTask(
+        val taskId: Int,
+        val taskBounds: Rect?,
+    ) : CaptureType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
new file mode 100644
index 0000000..2c0a0db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.ScreenshotData
+import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+private const val TAG = "PolicyRequestProcessor"
+
+/** A [ScreenshotRequestProcessor] which supports general policy rule matching. */
+class PolicyRequestProcessor(
+    @Background private val background: CoroutineDispatcher,
+    private val capture: ImageCapture,
+    private val displayTasks: DisplayContentRepository,
+    private val policies: List<CapturePolicy>,
+) : ScreenshotRequestProcessor {
+    override suspend fun process(original: ScreenshotData): ScreenshotData {
+        if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+            // The request contains an already captured screenshot, accept it as is.
+            Log.i(TAG, "Screenshot bitmap provided. No modifications applied.")
+            return original
+        }
+
+        val tasks = displayTasks.getDisplayContent(original.displayId)
+
+        // If policies yield explicit modifications, apply them and return the result
+        Log.i(TAG, "Applying policy checks....")
+        policies
+            .firstNotNullOfOrNull { policy -> policy.apply(tasks) }
+            ?.let {
+                Log.i(TAG, "Modifying screenshot: $it")
+                return apply(it, original)
+            }
+
+        // Otherwise capture normally, filling in additional information as needed.
+        return replaceWithScreenshot(
+            original = original,
+            componentName = original.topComponent ?: tasks.rootTasks.firstOrNull()?.topActivity,
+            owner = original.userHandle,
+            displayId = original.displayId
+        )
+    }
+
+    /** Produce a new [ScreenshotData] using [CaptureParameters] */
+    suspend fun apply(updates: CaptureParameters, original: ScreenshotData): ScreenshotData {
+        // Update and apply bitmap capture depending on the parameters.
+        val updated =
+            when (val type = updates.type) {
+                is IsolatedTask ->
+                    replaceWithTaskSnapshot(
+                        original,
+                        updates.component,
+                        updates.owner,
+                        type.taskId,
+                        type.taskBounds
+                    )
+                is FullScreen ->
+                    replaceWithScreenshot(
+                        original,
+                        updates.component,
+                        updates.owner,
+                        type.displayId
+                    )
+            }
+        return updated
+    }
+
+    suspend fun replaceWithTaskSnapshot(
+        original: ScreenshotData,
+        componentName: ComponentName?,
+        owner: UserHandle,
+        taskId: Int,
+        taskBounds: Rect?,
+    ): ScreenshotData {
+        val taskSnapshot = capture.captureTask(taskId)
+        return original.copy(
+            type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+            bitmap = taskSnapshot,
+            userHandle = owner,
+            taskId = taskId,
+            topComponent = componentName,
+            screenBounds = taskBounds
+        )
+    }
+
+    suspend fun replaceWithScreenshot(
+        original: ScreenshotData,
+        componentName: ComponentName?,
+        owner: UserHandle?,
+        displayId: Int,
+    ): ScreenshotData {
+        val screenshot = captureDisplay(displayId)
+        return original.copy(
+            type = TAKE_SCREENSHOT_FULLSCREEN,
+            bitmap = screenshot,
+            userHandle = owner,
+            topComponent = componentName,
+            screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0)
+        )
+    }
+
+    /** TODO: Move to ImageCapture (existing function is non-suspending) */
+    private suspend fun captureDisplay(displayId: Int): Bitmap? {
+        return withContext(background) { capture.captureDisplay(displayId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
new file mode 100644
index 0000000..221e647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.policy
+
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import javax.inject.Inject
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.firstOrNull
+
+/**
+ * Condition: When any visible task belongs to a private user.
+ *
+ * Parameters: Capture the whole screen, owned by the private user.
+ */
+class PrivateProfilePolicy
+@Inject
+constructor(
+    private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+    override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
+        // Find the first visible rootTaskInfo with a child task owned by a private user
+        val (rootTask, childTask) =
+            content.rootTasks
+                .filter { it.isVisible }
+                .firstNotNullOfOrNull { root ->
+                    root
+                        .childTasksTopDown()
+                        .firstOrNull {
+                            profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+                        }
+                        ?.let { root to it }
+                }
+                ?: return null
+
+        // If matched, return parameters needed to modify the request.
+        return CaptureParameters(
+            type = FullScreen(content.displayId),
+            component = childTask.componentName ?: rootTask.topActivity,
+            owner = UserHandle.of(childTask.userId),
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
new file mode 100644
index 0000000..d2f4d9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.policy
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.data.model.ChildTaskModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.map
+
+internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> {
+    return ((numActivities - 1) downTo 0).asFlow().map { index ->
+        ChildTaskModel(
+            childTaskIds[index],
+            childTaskNames[index],
+            childTaskBounds[index],
+            childTaskUserIds[index]
+        )
+    }
+}
+
+internal suspend fun RootTaskInfo.firstChildTaskOrNull(
+    filter: suspend (Int) -> Boolean
+): Pair<RootTaskInfo, Int>? {
+    // Child tasks are provided in bottom-up order
+    // Filtering is done top-down, so iterate backwards here.
+    for (index in numActivities - 1 downTo 0) {
+        if (filter(index)) {
+            return (this to index)
+        }
+    }
+    return null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index bc71ab7..63d1508 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.screenshot.policy
 
+import com.android.systemui.Flags.screenshotPrivateProfile
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.screenshot.ImageCapture
 import com.android.systemui.screenshot.RequestProcessor
 import com.android.systemui.screenshot.ScreenshotPolicy
@@ -29,6 +31,7 @@
 import dagger.Module
 import dagger.Provides
 import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
 
 @Module
 interface ScreenshotPolicyModule {
@@ -37,18 +40,42 @@
     @SysUISingleton
     fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository
 
-    companion object {
-        @Provides
-        @SysUISingleton
-        fun bindScreenshotRequestProcessor(
-            imageCapture: ImageCapture,
-            policyProvider: Provider<ScreenshotPolicy>,
-        ): ScreenshotRequestProcessor {
-            return RequestProcessor(imageCapture, policyProvider.get())
-        }
-    }
-
     @Binds
     @SysUISingleton
     fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
+
+    companion object {
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        fun bindCapturePolicyList(
+            privateProfilePolicy: PrivateProfilePolicy,
+            workProfilePolicy: WorkProfilePolicy,
+        ): List<CapturePolicy> {
+            // In order of priority. The first matching policy applies.
+            return listOf(workProfilePolicy, privateProfilePolicy)
+        }
+
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        fun bindScreenshotRequestProcessor(
+            @Background background: CoroutineDispatcher,
+            imageCapture: ImageCapture,
+            policyProvider: Provider<ScreenshotPolicy>,
+            displayContentRepoProvider: Provider<DisplayContentRepository>,
+            policyListProvider: Provider<List<CapturePolicy>>,
+        ): ScreenshotRequestProcessor {
+            return if (screenshotPrivateProfile()) {
+                PolicyRequestProcessor(
+                    background,
+                    imageCapture,
+                    displayContentRepoProvider.get(),
+                    policyListProvider.get()
+                )
+            } else {
+                RequestProcessor(imageCapture, policyProvider.get())
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
new file mode 100644
index 0000000..d6b5d6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.policy
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+
+/**
+ * Condition: When the top visible task (excluding PIP mode) belongs to a work user.
+ *
+ * Parameters: Capture only the foreground task, owned by the work user.
+ */
+class WorkProfilePolicy
+@Inject
+constructor(
+    private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+    override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
+        // Find the first non PiP rootTask with a top child task owned by a work user
+        val (rootTask, childTask) =
+            content.rootTasks
+                .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+                .map { it to it.childTasksTopDown().first() }
+                .firstOrNull { (_, child) ->
+                    profileTypes.getProfileType(child.userId) == ProfileType.WORK
+                }
+                ?: return null
+
+        // If matched, return parameters needed to modify the request.
+        return CaptureParameters(
+            type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
+            component = childTask.componentName ?: rootTask.topActivity,
+            owner = UserHandle.of(childTask.userId),
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 1e1a577..706ac9c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -335,8 +335,8 @@
             // TODO: Fix transition for work profile. Omitting it in the meantime.
             mActionExecutor.launchIntentAsync(
                     ActionIntentCreator.INSTANCE.createEdit(uri, this),
-                    null,
-                    mScreenshotUserHandle, false);
+                    mScreenshotUserHandle, false,
+                    /* activityOptions */ null, /* transitionCoordinator */ null);
         } else {
             String editorPackage = getString(R.string.config_screenshotEditor);
             Intent intent = new Intent(Intent.ACTION_EDIT);
@@ -363,7 +363,8 @@
 
     private void doShare(Uri uri) {
         Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri);
-        mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false);
+        mActionExecutor.launchIntentAsync(shareIntent, mScreenshotUserHandle, false,
+                /* activityOptions */ null, /* transitionCoordinator */ null);
     }
 
     private void onClicked(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
new file mode 100644
index 0000000..6c4ee3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.scroll
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.Log
+import android.view.ScrollCaptureResponse
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import javax.inject.Inject
+
+class ScrollCaptureExecutor
+@Inject
+constructor(
+    activityManager: ActivityManager,
+    private val scrollCaptureClient: ScrollCaptureClient,
+    private val scrollCaptureController: ScrollCaptureController,
+    private val longScreenshotHolder: LongScreenshotData,
+    @Main private val mainExecutor: Executor
+) {
+    private val isLowRamDevice = activityManager.isLowRamDevice
+    private var lastScrollCaptureRequest: ListenableFuture<ScrollCaptureResponse>? = null
+    private var lastScrollCaptureResponse: ScrollCaptureResponse? = null
+    private var longScreenshotFuture: ListenableFuture<LongScreenshot>? = null
+
+    fun requestScrollCapture(
+        displayId: Int,
+        token: IBinder,
+        callback: (ScrollCaptureResponse) -> Unit
+    ) {
+        if (!allowLongScreenshots()) {
+            Log.d(TAG, "Long screenshots not supported on this device")
+            return
+        }
+        scrollCaptureClient.setHostWindowToken(token)
+        lastScrollCaptureRequest?.cancel(true)
+        val scrollRequest =
+            scrollCaptureClient.request(displayId).apply {
+                addListener(
+                    { onScrollCaptureResponseReady(this)?.let { callback.invoke(it) } },
+                    mainExecutor
+                )
+            }
+        lastScrollCaptureRequest = scrollRequest
+    }
+
+    fun interface ScrollTransitionReady {
+        fun onTransitionReady(
+            destRect: Rect,
+            onTransitionEnd: Runnable,
+            longScreenshot: LongScreenshot
+        )
+    }
+
+    fun executeBatchScrollCapture(
+        response: ScrollCaptureResponse,
+        onCaptureComplete: Runnable,
+        onFailure: Runnable,
+        transition: ScrollTransitionReady,
+    ) {
+        // Clear the reference to prevent close() on reset
+        lastScrollCaptureResponse = null
+        longScreenshotFuture?.cancel(true)
+        longScreenshotFuture =
+            scrollCaptureController.run(response).apply {
+                addListener(
+                    {
+                        getLongScreenshotChecked(this, onFailure)?.let {
+                            longScreenshotHolder.setLongScreenshot(it)
+                            longScreenshotHolder.setTransitionDestinationCallback {
+                                destinationRect: Rect,
+                                onTransitionEnd: Runnable ->
+                                transition.onTransitionReady(destinationRect, onTransitionEnd, it)
+                            }
+                            onCaptureComplete.run()
+                        }
+                    },
+                    mainExecutor
+                )
+            }
+    }
+
+    fun close() {
+        lastScrollCaptureRequest?.cancel(true)
+        lastScrollCaptureRequest = null
+        lastScrollCaptureResponse?.close()
+        lastScrollCaptureResponse = null
+        longScreenshotFuture?.cancel(true)
+    }
+
+    private fun getLongScreenshotChecked(
+        future: ListenableFuture<LongScreenshot>,
+        onFailure: Runnable
+    ): LongScreenshot? {
+        var longScreenshot: LongScreenshot? = null
+        runCatching { longScreenshot = future.get() }
+            .onFailure {
+                Log.e(TAG, "Caught exception", it)
+                onFailure.run()
+                return null
+            }
+        if (longScreenshot?.height != 0) {
+            return longScreenshot
+        }
+        onFailure.run()
+        return null
+    }
+
+    private fun onScrollCaptureResponseReady(
+        responseFuture: Future<ScrollCaptureResponse>
+    ): ScrollCaptureResponse? {
+        try {
+            lastScrollCaptureResponse?.close()
+            lastScrollCaptureResponse = null
+            if (responseFuture.isCancelled) {
+                return null
+            }
+            val captureResponse = responseFuture.get().apply { lastScrollCaptureResponse = this }
+            if (!captureResponse.isConnected) {
+                // No connection means that the target window wasn't found
+                // or that it cannot support scroll capture.
+                Log.d(
+                    TAG,
+                    "ScrollCapture: ${captureResponse.description} [${captureResponse.windowTitle}]"
+                )
+                return null
+            }
+            Log.d(TAG, "ScrollCapture: connected to window [${captureResponse.windowTitle}]")
+            return captureResponse
+        } catch (e: InterruptedException) {
+            Log.e(TAG, "requestScrollCapture interrupted", e)
+        } catch (e: ExecutionException) {
+            Log.e(TAG, "requestScrollCapture failed", e)
+        }
+        return null
+    }
+
+    private fun allowLongScreenshots(): Boolean {
+        return !isLowRamDevice
+    }
+
+    private companion object {
+        private const val TAG = "ScrollCaptureExecutor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index c7fe3f6..3c5a0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -28,15 +28,16 @@
     fun bind(view: View, viewModel: ActionButtonViewModel) {
         val iconView = view.requireViewById<ImageView>(R.id.overlay_action_chip_icon)
         val textView = view.requireViewById<TextView>(R.id.overlay_action_chip_text)
-        iconView.setImageDrawable(viewModel.icon)
-        textView.text = viewModel.name
-        setMargins(iconView, textView, viewModel.name?.isNotEmpty() ?: false)
+        iconView.setImageDrawable(viewModel.appearance.icon)
+        textView.text = viewModel.appearance.label
+        setMargins(iconView, textView, viewModel.appearance.label?.isNotEmpty() ?: false)
         if (viewModel.onClicked != null) {
             view.setOnClickListener { viewModel.onClicked.invoke() }
         } else {
             view.setOnClickListener(null)
         }
-        view.contentDescription = viewModel.description
+        view.tag = viewModel.id
+        view.contentDescription = viewModel.appearance.description
         view.visibility = View.VISIBLE
         view.alpha = 1f
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index b191a1a..32e9296 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.screenshot.ui.binder
 
-import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -71,21 +70,36 @@
                                     .requireViewById<View>(R.id.actions_container_background)
                                     .visibility = View.VISIBLE
                             }
-                            val viewPool = actionsContainer.children.toList()
-                            actionsContainer.removeAllViews()
-                            val actionButtons =
-                                List(actions.size) {
-                                    viewPool.getOrElse(it) {
+
+                            // Remove any buttons not in the new list, then do another pass to add
+                            // any new actions and update any that are already there.
+                            // This assumes that actions can never change order and that each action
+                            // ID is unique.
+                            val newIds = actions.map { it.id }
+
+                            for (view in actionsContainer.children.toList()) {
+                                if (view.tag !in newIds) {
+                                    actionsContainer.removeView(view)
+                                }
+                            }
+
+                            for ((index, action) in actions.withIndex()) {
+                                val currentView: View? = actionsContainer.getChildAt(index)
+                                if (action.id == currentView?.tag) {
+                                    // Same ID, update the display
+                                    ActionButtonViewBinder.bind(currentView, action)
+                                } else {
+                                    // Different ID. Removals have already happened so this must
+                                    // mean that the new action must be inserted here.
+                                    val actionButton =
                                         layoutInflater.inflate(
                                             R.layout.overlay_action_chip,
                                             actionsContainer,
                                             false
                                         )
-                                    }
+                                    actionsContainer.addView(actionButton, index)
+                                    ActionButtonViewBinder.bind(actionButton, action)
                                 }
-                            actionButtons.zip(actions).forEach {
-                                actionsContainer.addView(it.first)
-                                ActionButtonViewBinder.bind(it.first, it.second)
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
index 5d2b0ad2..55a2ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
@@ -14,7 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.systemui.screenshot.ui.viewmodel
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+import android.graphics.drawable.Drawable
+
+/** Data describing how an action should be shown to the user. */
+data class ActionButtonAppearance(
+    val icon: Drawable?,
+    val label: CharSequence?,
+    val description: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index 05bfed1..64b0105 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -16,11 +16,19 @@
 
 package com.android.systemui.screenshot.ui.viewmodel
 
-import android.graphics.drawable.Drawable
-
 data class ActionButtonViewModel(
-    val icon: Drawable?,
-    val name: CharSequence?,
-    val description: CharSequence,
-    val onClicked: (() -> Unit)?
-)
+    val appearance: ActionButtonAppearance,
+    val id: Int,
+    val onClicked: (() -> Unit)?,
+) {
+    companion object {
+        private var nextId = 0
+
+        private fun getId() = nextId.also { nextId += 1 }
+
+        fun withNextId(
+            appearance: ActionButtonAppearance,
+            onClicked: (() -> Unit)?
+        ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), onClicked)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index ddfa69b..fa34803 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.screenshot.ui.viewmodel
 
 import android.graphics.Bitmap
+import android.util.Log
 import android.view.accessibility.AccessibilityManager
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -39,16 +40,34 @@
         _previewAction.value = onClick
     }
 
-    fun addAction(action: ActionButtonViewModel) {
+    fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int {
         val actionList = _actions.value.toMutableList()
+        val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked)
         actionList.add(action)
         _actions.value = actionList
+        return action.id
     }
 
-    fun addActions(actions: List<ActionButtonViewModel>) {
+    fun updateActionAppearance(actionId: Int, appearance: ActionButtonAppearance) {
         val actionList = _actions.value.toMutableList()
-        actionList.addAll(actions)
-        _actions.value = actionList
+        val index = actionList.indexOfFirst { it.id == actionId }
+        if (index >= 0) {
+            actionList[index] =
+                ActionButtonViewModel(appearance, actionId, actionList[index].onClicked)
+            _actions.value = actionList
+        } else {
+            Log.w(TAG, "Attempted to update unknown action id $actionId")
+        }
+    }
+
+    fun removeAction(actionId: Int) {
+        val actionList = _actions.value.toMutableList()
+        if (actionList.removeIf { it.id == actionId }) {
+            // Update if something was removed.
+            _actions.value = actionList
+        } else {
+            Log.w(TAG, "Attempted to remove unknown action id $actionId")
+        }
     }
 
     fun reset() {
@@ -56,4 +75,8 @@
         _previewAction.value = null
         _actions.value = listOf()
     }
+
+    companion object {
+        const val TAG = "ScreenshotViewModel"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 92d6ec97..8397d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -52,7 +52,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.settings.SecureSettings;
 
 import dagger.assisted.Assisted;
@@ -107,7 +106,7 @@
     private ValueAnimator mSliderAnimator;
 
     @Override
-    public void setMirror(BrightnessMirrorController controller) {
+    public void setMirror(@Nullable MirrorController controller) {
         mControl.setMirrorControllerAndMirror(controller);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
index 701d814..073279b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -16,12 +16,9 @@
 
 package com.android.systemui.settings.brightness
 
-import com.android.systemui.statusbar.policy.BrightnessMirrorController
-import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
-
 class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController) {
 
-    var mirrorController: BrightnessMirrorController? = null
+    var mirrorController: MirrorController? = null
         private set
 
     var brightnessController: MirroredBrightnessController = brightnessController
@@ -30,7 +27,8 @@
             updateBrightnessMirror()
         }
 
-    private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+    private val brightnessMirrorListener =
+            MirrorController.BrightnessMirrorListener { updateBrightnessMirror() }
 
     fun onQsPanelAttached() {
         mirrorController?.addCallback(brightnessMirrorListener)
@@ -40,7 +38,7 @@
         mirrorController?.removeCallback(brightnessMirrorListener)
     }
 
-    fun setController(controller: BrightnessMirrorController?) {
+    fun setController(controller: MirrorController?) {
         mirrorController?.removeCallback(brightnessMirrorListener)
         mirrorController = controller
         mirrorController?.addCallback(brightnessMirrorListener)
@@ -48,6 +46,6 @@
     }
 
     private fun updateBrightnessMirror() {
-        mirrorController?.let { brightnessController.setMirror(it) }
+        brightnessController.setMirror(mirrorController)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 539b0c2..b425fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -57,8 +57,10 @@
         ToggleSlider {
 
     private Listener mListener;
+    @Nullable
     private ToggleSlider mMirror;
-    private BrightnessMirrorController mMirrorController;
+    @Nullable
+    private MirrorController mMirrorController;
     private boolean mTracking;
     private final FalsingManager mFalsingManager;
     private final UiEventLogger mUiEventLogger;
@@ -108,6 +110,9 @@
     protected void onViewAttached() {
         mView.setOnSeekBarChangeListener(mSeekListener);
         mView.setOnInterceptListener(mOnInterceptListener);
+        if (mMirror != null) {
+            mView.setOnDispatchTouchEventListener(this::mirrorTouchEvent);
+        }
     }
 
     @Override
@@ -129,7 +134,10 @@
 
     private boolean copyEventToMirror(MotionEvent ev) {
         MotionEvent copy = ev.copy();
-        boolean out = mMirror.mirrorTouchEvent(copy);
+        boolean out = false;
+        if (mMirror != null) {
+            out = mMirror.mirrorTouchEvent(copy);
+        }
         copy.recycle();
         return out;
     }
@@ -166,9 +174,13 @@
      * @param c
      */
     @Override
-    public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
+    public void setMirrorControllerAndMirror(@Nullable MirrorController c) {
         mMirrorController = c;
-        setMirror(c.getToggleSlider());
+        if (c != null) {
+            setMirror(c.getToggleSlider());
+        } else {
+            setMirror(null);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
new file mode 100644
index 0000000..6a9af26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness
+
+import android.view.View
+import com.android.systemui.settings.brightness.MirrorController.BrightnessMirrorListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface MirrorController : CallbackController<BrightnessMirrorListener> {
+
+    /**
+     * Get the [ToggleSlider] currently associated with this controller, or `null` if none currently
+     */
+    fun getToggleSlider(): ToggleSlider?
+
+    /**
+     * Indicate to this controller that the user is dragging on the brightness view and the mirror
+     * should show
+     */
+    fun showMirror()
+
+    /**
+     * Indicate to this controller that the user has stopped dragging on the brightness view and the
+     * mirror should hide
+     */
+    fun hideMirror()
+
+    /**
+     * Set the location and size of the current brightness [view] in QS so it can be properly
+     * adapted to show the mirror in the same location and with the same size.
+     */
+    fun setLocationAndSize(view: View)
+
+    fun interface BrightnessMirrorListener {
+        fun onBrightnessMirrorReinflated(brightnessMirror: View?)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
index 8d857de..b1a532b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -22,5 +22,5 @@
  * Indicates controller that has brightness slider and uses [BrightnessMirrorController]
  */
 interface MirroredBrightnessController {
-    fun setMirror(controller: BrightnessMirrorController)
+    fun setMirror(controller: MirrorController?)
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 648e33b..24bc670 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -19,7 +19,6 @@
 import android.view.MotionEvent;
 
 import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
 public interface ToggleSlider {
     interface Listener {
@@ -27,7 +26,7 @@
     }
 
     void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin);
-    void setMirrorControllerAndMirror(BrightnessMirrorController c);
+    void setMirrorControllerAndMirror(MirrorController c);
     boolean mirrorTouchEvent(MotionEvent ev);
 
     void setOnChangedListener(Listener l);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
new file mode 100644
index 0000000..a0c9be4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorShowingRepository @Inject constructor() {
+    private val _isShowing = MutableStateFlow(false)
+    val isShowing = _isShowing.asStateFlow()
+
+    fun setMirrorShowing(showing: Boolean) {
+        _isShowing.value = showing
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
new file mode 100644
index 0000000..ef6e72f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessMirrorShowingInteractor
+@Inject
+constructor(
+    private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository,
+) {
+    val isShowing = brightnessMirrorShowingRepository.isShowing
+
+    fun setMirrorShowing(showing: Boolean) {
+        brightnessMirrorShowingRepository.setMirrorShowing(showing)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
new file mode 100644
index 0000000..468a873
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.ui.binder
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+
+object BrightnessMirrorInflater {
+
+    fun inflate(
+        context: Context,
+        sliderControllerFactory: BrightnessSliderController.Factory,
+    ): Pair<View, BrightnessSliderController> {
+        val frame =
+            (LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null)
+                    as ViewGroup)
+                .apply { isVisible = true }
+        val sliderController = sliderControllerFactory.create(context, frame)
+        sliderController.init()
+        frame.addView(
+            sliderController.rootView,
+            ViewGroup.LayoutParams.MATCH_PARENT,
+            ViewGroup.LayoutParams.WRAP_CONTENT
+        )
+        return frame to sliderController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
new file mode 100644
index 0000000..2651a994
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.ui.viewModel
+
+import android.content.res.Resources
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.settings.brightness.ToggleSlider
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorViewModel
+@Inject
+constructor(
+    private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+    @Main private val resources: Resources,
+    val sliderControllerFactory: BrightnessSliderController.Factory,
+) : MirrorController {
+
+    private val tempPosition = IntArray(2)
+
+    private var _toggleSlider: BrightnessSliderController? = null
+
+    val isShowing = brightnessMirrorShowingInteractor.isShowing
+
+    private val _locationAndSize: MutableStateFlow<LocationAndSize> =
+        MutableStateFlow(LocationAndSize())
+    val locationAndSize = _locationAndSize.asStateFlow()
+
+    override fun getToggleSlider(): ToggleSlider? {
+        return _toggleSlider
+    }
+
+    fun setToggleSlider(toggleSlider: BrightnessSliderController) {
+        _toggleSlider = toggleSlider
+    }
+
+    override fun showMirror() {
+        brightnessMirrorShowingInteractor.setMirrorShowing(true)
+    }
+
+    override fun hideMirror() {
+        brightnessMirrorShowingInteractor.setMirrorShowing(false)
+    }
+
+    override fun setLocationAndSize(view: View) {
+        view.getLocationInWindow(tempPosition)
+        val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+        _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding)
+        // Account for desired padding
+        _locationAndSize.value =
+            LocationAndSize(
+                yOffset = tempPosition[1] - padding,
+                width = view.measuredWidth + 2 * padding,
+                height = view.measuredHeight + 2 * padding,
+            )
+    }
+
+    // Callbacks are used for indicating reinflation when the config changes in some ways (like
+    // density). However, we don't need that as we recompose the view anyway
+    override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {}
+
+    override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
+}
+
+data class LocationAndSize(
+    val yOffset: Int = 0,
+    val width: Int = 0,
+    val height: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index f9b4e67..903af61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -81,6 +81,8 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
+        mInteractionEventHandler.collectKeyEvent(event);
+
         if (mInteractionEventHandler.interceptMediaKey(event)) {
             return true;
         }
@@ -301,6 +303,11 @@
         boolean dispatchKeyEvent(KeyEvent event);
 
         boolean dispatchKeyEventPreIme(KeyEvent event);
+
+        /**
+         * Collects the KeyEvent without intercepting it
+         */
+        void collectKeyEvent(KeyEvent event);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 324dfdf..6ac81d2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -563,6 +563,11 @@
             public boolean dispatchKeyEvent(KeyEvent event) {
                 return mSysUIKeyEventHandler.dispatchKeyEvent(event);
             }
+
+            @Override
+            public void collectKeyEvent(KeyEvent event) {
+                mFalsingCollector.onKeyEvent(event);
+            }
         });
 
         mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 9362cd0..980f665a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -57,6 +58,7 @@
     val qsSceneAdapter: QSSceneAdapter,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
+    val brightnessMirrorViewModel: BrightnessMirrorViewModel,
     val mediaDataManager: MediaDataManager,
     shadeInteractor: ShadeInteractor,
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index f689e7e..968b591 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.row.FooterViewButton;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.util.DrawableDumpKt;
@@ -457,6 +458,12 @@
          */
         public boolean hideContent;
 
+        /**
+         * When true, skip animating Y on the next #animateTo.
+         * Once true, remains true until reset in #animateTo.
+         */
+        public boolean resetY = false;
+
         @Override
         public void copyFrom(ViewState viewState) {
             super.copyFrom(viewState);
@@ -473,5 +480,17 @@
                 footerView.setContentVisibleAnimated(!hideContent);
             }
         }
+
+        @Override
+        public void animateTo(View child, AnimationProperties properties) {
+            if (child instanceof FooterView) {
+                // Must set animateY=false before super.animateTo, which checks for animateY
+                if (resetY) {
+                    properties.getAnimationFilter().animateY = false;
+                    resetY = false;
+                }
+            }
+            super.animateTo(child, properties);
+        }
     }
 }
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 eb6c7b5..9e0b16c 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
@@ -1767,7 +1767,7 @@
      */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         this(context, attrs, context);
-        Log.wtf(TAG, "This constructor shouldn't be called");
+        Log.e(TAG, "This constructor shouldn't be called");
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 03a1082..0c248f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -30,7 +30,7 @@
     public static final int NO_DELAY = -1;
     boolean animateAlpha;
     boolean animateX;
-    boolean animateY;
+    public boolean animateY;
     ArraySet<View> animateYViews = new ArraySet<>();
     boolean animateZ;
     boolean animateHeight;
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 5dc37e0..92c597c 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
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 
@@ -1429,7 +1430,7 @@
             if (singleLineView != null) {
                 minExpandHeight += singleLineView.getHeight();
             } else {
-                if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                if (AsyncHybridViewInflation.isEnabled()) {
                     minExpandHeight += mMinSingleLineHeight;
                 } else {
                     Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
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 fedb88d..82559de 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
@@ -1868,6 +1868,13 @@
     private void updateImeInset(WindowInsets windowInsets) {
         mImeInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
 
+        if (mFooterView != null && mFooterView.getViewState() != null) {
+            // Do not animate footer Y when showing IME so that after IME hides, the footer
+            // appears at the correct Y. Once resetY is true, it remains true (even when IME
+            // hides, where mImeInset=0) until reset in FooterViewState#animateTo.
+            ((FooterView.FooterViewState) mFooterView.getViewState()).resetY |= mImeInset > 0;
+        }
+
         if (mForcedScroll != null) {
             updateForcedScroll();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
index 7e3e724..832e690 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
@@ -30,11 +30,15 @@
     /** The current height of the notification container. */
     val height: Float = bottom - top
 
-    operator fun minus(position: ViewPosition) =
-        ShadeScrimBounds(
-            left = left - position.left,
-            top = top - position.top,
-            right = right - position.left,
-            bottom = bottom - position.top,
-        )
+    fun minus(leftOffset: Int = 0, topOffset: Int = 0) =
+        if (leftOffset == 0 && topOffset == 0) {
+            this
+        } else {
+            ShadeScrimBounds(
+                left = this.left - leftOffset,
+                top = this.top - topOffset,
+                right = this.right - leftOffset,
+                bottom = this.bottom - topOffset,
+            )
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a98717a..047b560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
-import android.view.View
+import android.util.Log
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.common.ui.ConfigurationState
@@ -26,7 +26,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
 import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -37,9 +36,6 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
 /** Binds the [NotificationScrollView]. */
@@ -54,8 +50,15 @@
     private val configuration: ConfigurationState,
 ) : FlowDumperImpl(dumpManager) {
 
-    private val viewPosition = MutableStateFlow(ViewPosition()).dumpValue("viewPosition")
-    private val viewTopOffset = viewPosition.map { it.top }.distinctUntilChanged()
+    private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset")
+
+    private fun updateViewPosition() {
+        val trueView = view.asView()
+        if (trueView.top != 0) {
+            Log.w("NSSL", "Expected $trueView to have top==0")
+        }
+        viewLeftOffset.value = trueView.left
+    }
 
     fun bindWhileAttached(): DisposableHandle {
         return view.asView().repeatWhenAttached(mainImmediateDispatcher) {
@@ -65,20 +68,20 @@
 
     suspend fun bind() = coroutineScope {
         launchAndDispose {
-            viewPosition.value = view.asView().position
-            view.asView().onLayoutChanged { viewPosition.value = it.position }
+            updateViewPosition()
+            view.asView().onLayoutChanged { updateViewPosition() }
         }
 
         launch {
-            viewModel.shadeScrimShape(scrimRadius, viewPosition).collect {
-                view.setScrimClippingShape(it)
-            }
+            viewModel
+                .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
+                .collect { view.setScrimClippingShape(it) }
         }
 
-        launch { viewModel.stackTop.minusTopOffset().collect { view.setStackTop(it) } }
-        launch { viewModel.stackBottom.minusTopOffset().collect { view.setStackBottom(it) } }
+        launch { viewModel.stackTop.collect { view.setStackTop(it) } }
+        launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
         launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
-        launch { viewModel.headsUpTop.minusTopOffset().collect { view.setHeadsUpTop(it) } }
+        launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
         launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } }
         launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
 
@@ -94,15 +97,7 @@
         }
     }
 
-    /** Combine with the topOffset flow and subtract that value from this flow's value */
-    private fun Flow<Float>.minusTopOffset() =
-        combine(viewTopOffset) { y, topOffset -> y - topOffset }
-
     /** flow of the scrim clipping radius */
     private val scrimRadius: Flow<Int>
         get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
-
-    /** Construct a [ViewPosition] from this view using [View.getLeft] and [View.getTop] */
-    private val View.position
-        get() = ViewPosition(left = left, top = top)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index cfd19ba..741102b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -21,11 +21,14 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.Flags.communalHub
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -137,10 +140,12 @@
                         }
                     }
 
-                    launch {
-                        burnInParams
-                            .flatMapLatest { params -> viewModel.translationY(params) }
-                            .collect { y -> controller.setTranslationY(y) }
+                    if (!SceneContainerFlag.isEnabled) {
+                        launch {
+                            burnInParams
+                                .flatMapLatest { params -> viewModel.translationY(params) }
+                                .collect { y -> controller.setTranslationY(y) }
+                        }
                     }
 
                     launch { viewModel.translationX.collect { x -> controller.translationX = x } }
@@ -168,22 +173,16 @@
         controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() }
         disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) }
 
-        view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
-            val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
-            burnInParams.update { current ->
-                current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+        disposables +=
+            view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+                val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+                burnInParams.update { current ->
+                    current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+                }
+                insets
             }
-            insets
-        }
-        disposables += DisposableHandle { view.setOnApplyWindowInsetsListener(null) }
 
-        // Required to capture keyguard media changes and ensure the notification count is correct
-        val layoutChangeListener =
-            View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
-                viewModel.notificationStackChanged()
-            }
-        view.addOnLayoutChangeListener(layoutChangeListener)
-        disposables += DisposableHandle { view.removeOnLayoutChangeListener(layoutChangeListener) }
+        disposables += view.onLayoutChanged { viewModel.notificationStackChanged() }
 
         return disposables
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 9483f33..516ec31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -92,12 +91,12 @@
 
     fun shadeScrimShape(
         cornerRadius: Flow<Int>,
-        viewPosition: Flow<ViewPosition>
+        viewLeftOffset: Flow<Int>
     ): Flow<ShadeScrimShape?> =
-        combine(shadeScrimClipping, cornerRadius, viewPosition) { clipping, radius, position ->
+        combine(shadeScrimClipping, cornerRadius, viewLeftOffset) { clipping, radius, leftOffset ->
                 if (clipping == null) return@combine null
                 ShadeScrimShape(
-                    bounds = clipping.bounds - position,
+                    bounds = clipping.bounds.minus(leftOffset = leftOffset),
                     topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0,
                     bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 692368d..0486ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -19,6 +19,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -166,23 +167,35 @@
             )
             .dumpWhileCollecting("isShadeLocked")
 
+    @VisibleForTesting
+    val paddingTopDimen: Flow<Int> =
+        interactor.configurationBasedDimensions
+            .map {
+                when {
+                    !it.useSplitShade -> 0
+                    it.useLargeScreenHeader -> it.marginTopLargeScreen
+                    else -> it.marginTop
+                }
+            }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("paddingTopDimen")
+
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         interactor.configurationBasedDimensions
             .map {
                 val marginTop =
-                    if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop
+                    when {
+                        // y position of the NSSL in the window needs to be 0 under scene container
+                        SceneContainerFlag.isEnabled -> 0
+                        it.useLargeScreenHeader -> it.marginTopLargeScreen
+                        else -> it.marginTop
+                    }
                 ConfigurationBasedDimensions(
                     marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
                     marginEnd = it.marginHorizontal,
                     marginBottom = it.marginBottom,
                     marginTop = marginTop,
                     useSplitShade = it.useSplitShade,
-                    paddingTop =
-                        if (it.useSplitShade) {
-                            marginTop
-                        } else {
-                            0
-                        },
                 )
             }
             .distinctUntilChanged()
@@ -341,16 +354,16 @@
         combine(
                 isOnLockscreenWithoutShade,
                 keyguardInteractor.notificationContainerBounds,
-                configurationBasedDimensions,
+                paddingTopDimen,
                 interactor.topPosition
                     .sampleCombine(
                         keyguardTransitionInteractor.isInTransitionToAnyState,
                         shadeInteractor.qsExpansion,
                     )
                     .onStart { emit(Triple(0f, false, 0f)) }
-            ) { onLockscreen, bounds, config, (top, isInTransitionToAnyState, qsExpansion) ->
+            ) { onLockscreen, bounds, paddingTop, (top, isInTransitionToAnyState, qsExpansion) ->
                 if (onLockscreen) {
-                    bounds.copy(top = bounds.top - config.paddingTop)
+                    bounds.copy(top = bounds.top - paddingTop)
                 } else {
                     // When QS expansion > 0, it should directly set the top padding so do not
                     // animate it
@@ -367,10 +380,6 @@
                 initialValue = NotificationContainerBounds(),
             )
             .dumpValue("bounds")
-        get() {
-            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode()
-            return field
-        }
 
     /**
      * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out
@@ -546,6 +555,8 @@
      * translated as the keyguard fades out.
      */
     fun translationY(params: BurnInParameters): Flow<Float> {
+        // with SceneContainer, x translation is handled by views, y is handled by compose
+        SceneContainerFlag.assertInLegacyMode()
         return combine(
                 aodBurnInViewModel
                     .movement(params)
@@ -644,6 +655,5 @@
         val marginEnd: Int,
         val marginBottom: Int,
         val useSplitShade: Boolean,
-        val paddingTop: Int,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index a1fae03..2651d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -103,8 +103,8 @@
     /**
      * Returns an ActivityOptions bundle created using the given parameters.
      *
-     * @param displayId The ID of the display to launch the activity in. Typically this would
-     *                  be the display the status bar is on.
+     * @param displayId        The ID of the display to launch the activity in. Typically this would
+     *                         be the display the status bar is on.
      * @param animationAdapter The animation adapter used to start this activity, or {@code null}
      *                         for the default animation.
      */
@@ -197,7 +197,7 @@
 
     void onKeyguardViewManagerStatesUpdated();
 
-    /** */
+    /**  */
     boolean getCommandQueuePanelsEnabled();
 
     void showWirelessChargingAnimation(int batteryLevel);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5baf6a0..1d6b744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,6 +168,7 @@
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -594,6 +595,8 @@
 
     private final SceneContainerFlags mSceneContainerFlags;
 
+    private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
+
     /**
      * Public constructor for CentralSurfaces.
      *
@@ -705,7 +708,8 @@
             UserTracker userTracker,
             Provider<FingerprintManager> fingerprintManager,
             ActivityStarter activityStarter,
-            SceneContainerFlags sceneContainerFlags
+            SceneContainerFlags sceneContainerFlags,
+            BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
     ) {
         mContext = context;
         mNotificationsController = notificationsController;
@@ -801,6 +805,7 @@
         mFingerprintManager = fingerprintManager;
         mActivityStarter = activityStarter;
         mSceneContainerFlags = sceneContainerFlags;
+        mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1083,6 +1088,12 @@
         mJavaAdapter.alwaysCollectFlow(
                 mCommunalInteractor.isIdleOnCommunal(),
                 mIdleOnCommunalConsumer);
+        if (mSceneContainerFlags.isEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mBrightnessMirrorShowingInteractor.isShowing(),
+                    this::setBrightnessMirrorShowing
+            );
+        }
     }
 
     /**
@@ -1284,10 +1295,7 @@
                     mShadeSurface,
                     mNotificationShadeDepthControllerLazy.get(),
                     mBrightnessSliderFactory,
-                    (visible) -> {
-                        mBrightnessMirrorVisible = visible;
-                        updateScrimController();
-                    });
+                    this::setBrightnessMirrorShowing);
             fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
                 QS qs = (QS) f;
                 if (qs instanceof QSFragmentLegacy) {
@@ -1346,6 +1354,10 @@
         ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
     }
 
+    private void setBrightnessMirrorShowing(boolean showing) {
+        mBrightnessMirrorVisible = showing;
+        updateScrimController();
+    }
 
     /**
      * When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f.
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 495b4e1..4c3c7d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -421,9 +422,12 @@
     public void updateHeader(NotificationEntry entry) {
         ExpandableNotificationRow row = entry.getRow();
         float headerVisibleAmount = 1.0f;
-        if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
-                || row.showingPulsing()) {
-            headerVisibleAmount = mAppearFraction;
+        // To fix the invisible HUN group header issue
+        if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+            if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
+                    || row.showingPulsing()) {
+                headerVisibleAmount = mAppearFraction;
+            }
         }
         row.setHeaderVisibleAmount(headerVisibleAmount);
     }
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 13f76fe..7d920ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -27,6 +27,7 @@
 
 import com.android.systemui.res.R;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
 import com.android.systemui.settings.brightness.ToggleSlider;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeViewController;
@@ -38,8 +39,7 @@
 /**
  * Controls showing and hiding of the brightness mirror.
  */
-public class BrightnessMirrorController
-        implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
+public class BrightnessMirrorController implements MirrorController {
 
     private final NotificationShadeWindowView mStatusBarWindow;
     private final Consumer<Boolean> mVisibilityCallback;
@@ -71,6 +71,7 @@
         updateResources();
     }
 
+    @Override
     public void showMirror() {
         mBrightnessMirror.setVisibility(View.VISIBLE);
         mVisibilityCallback.accept(true);
@@ -78,16 +79,14 @@
         mDepthController.setBrightnessMirrorVisible(true);
     }
 
+    @Override
     public void hideMirror() {
         mVisibilityCallback.accept(false);
         mNotificationPanel.setAlpha(255, true /* animate */);
         mDepthController.setBrightnessMirrorVisible(false);
     }
 
-    /**
-     * Set the location and size of the mirror container to match that of the slider in QS
-     * @param original the original view in QS
-     */
+    @Override
     public void setLocationAndSize(View original) {
         original.getLocationInWindow(mInt2Cache);
 
@@ -112,6 +111,7 @@
         }
     }
 
+    @Override
     public ToggleSlider getToggleSlider() {
         return mToggleSliderController;
     }
@@ -176,8 +176,4 @@
     public void onUiModeChanged() {
         reinflate();
     }
-
-    public interface BrightnessMirrorListener {
-        void onBrightnessMirrorReinflated(View brightnessMirror);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index e977014..aea739d 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -20,11 +20,10 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.annotation.BinderThread
-import android.content.Context
-import android.os.Handler
 import android.os.SystemProperties
 import android.util.Log
 import android.view.animation.DecelerateInterpolator
+import com.android.app.tracing.TraceUtils.traceAsync
 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,12 +35,13 @@
 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
 import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.kotlin.race
 import javax.inject.Inject
 import kotlin.coroutines.resume
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -59,13 +59,13 @@
 class FoldLightRevealOverlayAnimation
 @Inject
 constructor(
-    private val context: Context,
-    @UnfoldBg private val bgHandler: Handler,
+    @UnfoldBg private val bgDispatcher: CoroutineDispatcher,
     private val deviceStateRepository: DeviceStateRepository,
     private val powerInteractor: PowerInteractor,
     @Background private val applicationScope: CoroutineScope,
     private val animationStatusRepository: AnimationStatusRepository,
-    private val controllerFactory: FullscreenLightRevealAnimationController.Factory
+    private val controllerFactory: FullscreenLightRevealAnimationController.Factory,
+    private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider
 ) : FullscreenLightRevealAnimation {
 
     private val revealProgressValueAnimator: ValueAnimator =
@@ -79,7 +79,7 @@
     override fun init() {
         // This method will be called only on devices where this animation is enabled,
         // so normally this thread won't be created
-        if (!FoldLockSettingAvailabilityProvider(context.resources).isFoldLockBehaviorAvailable) {
+        if (!foldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) {
             return
         }
 
@@ -91,7 +91,6 @@
             )
         controller.init()
 
-        val bgDispatcher = bgHandler.asCoroutineDispatcher("@UnfoldBg Handler")
         applicationScope.launch(bgDispatcher) {
             powerInteractor.screenPowerState.collect {
                 if (it == ScreenPowerState.SCREEN_ON) {
@@ -109,14 +108,21 @@
                             if (!areAnimationEnabled.first() || !isFolded) {
                                 return@flow
                             }
-                            withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
-                                readyCallback = CompletableDeferred()
-                                val onReady = readyCallback?.await()
-                                readyCallback = null
-                                controller.addOverlay(ALPHA_OPAQUE, onReady)
-                                waitForScreenTurnedOn()
-                            }
-                            playFoldLightRevealOverlayAnimation()
+                            race(
+                                {
+                                    traceAsync(TAG, "prepareAndPlayFoldAnimation()") {
+                                        withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+                                            readyCallback = CompletableDeferred()
+                                            val onReady = readyCallback?.await()
+                                            readyCallback = null
+                                            controller.addOverlay(ALPHA_OPAQUE, onReady)
+                                            waitForScreenTurnedOn()
+                                        }
+                                        playFoldLightRevealOverlayAnimation()
+                                    }
+                                },
+                                { waitForGoToSleep() }
+                            )
                         }
                         .catchTimeoutAndLog()
                         .onCompletion {
@@ -135,9 +141,13 @@
         readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
     }
 
-    private suspend fun waitForScreenTurnedOn() {
-        powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
-    }
+    private suspend fun waitForScreenTurnedOn() =
+        traceAsync(TAG, "waitForScreenTurnedOn()") {
+            powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+        }
+
+    private suspend fun waitForGoToSleep() =
+        traceAsync(TAG, "waitForGoToSleep()") { powerInteractor.isAsleep.filter { it }.first() }
 
     private suspend fun playFoldLightRevealOverlayAnimation() {
         revealProgressValueAnimator.duration = ANIMATION_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 9bd0e32..3522850 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
 import android.os.SystemProperties
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
 import com.android.systemui.dagger.qualifiers.Application
@@ -175,6 +176,12 @@
     fun provideDisplaySwitchLatencyLogger(): DisplaySwitchLatencyLogger =
         DisplaySwitchLatencyLogger()
 
+    @Provides
+    @Singleton
+    fun provideFoldLockSettingAvailabilityProvider(
+        context: Context
+    ): FoldLockSettingAvailabilityProvider = FoldLockSettingAvailabilityProvider(context.resources)
+
     @Module
     interface Bindings {
         @Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9bfc4ce..1568e8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -20,8 +20,12 @@
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.app.WallpaperManager.SetWallpaperFlags;
 
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
+import android.annotation.Nullable;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.RecordingCanvas;
@@ -134,6 +138,12 @@
                     mLongExecutor,
                     mLock,
                     new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
+
+                        @Override
+                        public void onColorsProcessed() {
+                            CanvasEngine.this.notifyColorsChanged();
+                        }
+
                         @Override
                         public void onColorsProcessed(List<RectF> regions,
                                 List<WallpaperColors> colors) {
@@ -183,8 +193,11 @@
 
         @Override
         public void onDestroy() {
-            getDisplayContext().getSystemService(DisplayManager.class)
-                    .unregisterDisplayListener(this);
+            Context context = getDisplayContext();
+            if (context != null) {
+                DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+                if (displayManager != null) displayManager.unregisterDisplayListener(this);
+            }
             mWallpaperLocalColorExtractor.cleanUp();
         }
 
@@ -429,6 +442,12 @@
         }
 
         @Override
+        public @Nullable WallpaperColors onComputeColors() {
+            if (!offloadColorExtraction()) return null;
+            return mWallpaperLocalColorExtractor.onComputeColors();
+        }
+
+        @Override
         public boolean supportsLocalColorExtraction() {
             return true;
         }
@@ -465,6 +484,12 @@
         }
 
         @Override
+        public void onDimAmountChanged(float dimAmount) {
+            if (!offloadColorExtraction()) return;
+            mWallpaperLocalColorExtractor.onDimAmountChanged(dimAmount);
+        }
+
+        @Override
         public void onDisplayAdded(int displayId) {
 
         }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
index e2ec8dc..d37dfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
@@ -17,6 +17,8 @@
 
 package com.android.systemui.wallpapers;
 
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
 import android.app.WallpaperColors;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -66,6 +68,12 @@
     private final List<RectF> mPendingRegions = new ArrayList<>();
     private final Set<RectF> mProcessedRegions = new ArraySet<>();
 
+    private float mWallpaperDimAmount = 0f;
+    private WallpaperColors mWallpaperColors;
+
+    // By default we assume that colors were loaded from disk and don't need to be recomputed
+    private boolean mRecomputeColors = false;
+
     @LongRunning
     private final Executor mLongExecutor;
 
@@ -75,6 +83,12 @@
      * Interface to handle the callbacks after the different steps of the color extraction
      */
     public interface WallpaperLocalColorExtractorCallback {
+
+        /**
+         * Callback after the wallpaper colors have been computed
+         */
+        void onColorsProcessed();
+
         /**
          * Callback after the colors of new regions have been extracted
          * @param regions the list of new regions that have been processed
@@ -129,7 +143,7 @@
             if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
             mDisplayWidth = displayWidth;
             mDisplayHeight = displayHeight;
-            processColorsInternal();
+            processLocalColorsInternal();
         }
     }
 
@@ -166,7 +180,8 @@
             mBitmapHeight = bitmap.getHeight();
             mMiniBitmap = createMiniBitmap(bitmap);
             mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
-            recomputeColors();
+            if (offloadColorExtraction() && mRecomputeColors) recomputeColorsInternal();
+            recomputeLocalColors();
         }
     }
 
@@ -184,16 +199,66 @@
             if (mPages == pages) return;
             mPages = pages;
             if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
-                recomputeColors();
+                recomputeLocalColors();
             }
         }
     }
 
-    // helper to recompute colors, to be called in synchronized methods
-    private void recomputeColors() {
+    /**
+     * Should be called when the dim amount of the wallpaper changes, to recompute the colors
+     */
+    public void onDimAmountChanged(float dimAmount) {
+        mLongExecutor.execute(() -> onDimAmountChangedSynchronized(dimAmount));
+    }
+
+    private void onDimAmountChangedSynchronized(float dimAmount) {
+        synchronized (mLock) {
+            if (mWallpaperDimAmount == dimAmount) return;
+            mWallpaperDimAmount = dimAmount;
+            mRecomputeColors = true;
+            recomputeColorsInternal();
+        }
+    }
+
+    /**
+     * To be called by {@link ImageWallpaper.CanvasEngine#onComputeColors}. This will either
+     * return the current wallpaper colors, or if the bitmap is not yet loaded, return null and call
+     * {@link WallpaperLocalColorExtractorCallback#onColorsProcessed()} when the colors are ready.
+     */
+    public WallpaperColors onComputeColors() {
+        mLongExecutor.execute(this::onComputeColorsSynchronized);
+        return mWallpaperColors;
+    }
+
+    private void onComputeColorsSynchronized() {
+        synchronized (mLock) {
+            if (mRecomputeColors) return;
+            mRecomputeColors = true;
+            recomputeColorsInternal();
+        }
+    }
+
+    /**
+     * helper to recompute main colors, to be called in synchronized methods
+     */
+    private void recomputeColorsInternal() {
+        if (mMiniBitmap == null) return;
+        mWallpaperColors = getWallpaperColors(mMiniBitmap, mWallpaperDimAmount);
+        mWallpaperLocalColorExtractorCallback.onColorsProcessed();
+    }
+
+    @VisibleForTesting
+    WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap, float dimAmount) {
+        return WallpaperColors.fromBitmap(bitmap, dimAmount);
+    }
+
+    /**
+     * helper to recompute local colors, to be called in synchronized methods
+     */
+    private void recomputeLocalColors() {
         mPendingRegions.addAll(mProcessedRegions);
         mProcessedRegions.clear();
-        processColorsInternal();
+        processLocalColorsInternal();
     }
 
     /**
@@ -216,7 +281,7 @@
             if (!wasActive && isActive()) {
                 mWallpaperLocalColorExtractorCallback.onActivated();
             }
-            processColorsInternal();
+            processLocalColorsInternal();
         }
     }
 
@@ -353,7 +418,7 @@
      * then notify the callback with the resulting colors for these regions
      * This method should only be called synchronously
      */
-    private void processColorsInternal() {
+    private void processLocalColorsInternal() {
         /*
          * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
          * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index ca55dd8..9a83311 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -362,6 +362,27 @@
         }
 
     @Test
+    fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
+                    .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToLockscreenTransition(this)
+            transitionStep.value =
+                    TransitionStep(
+                            from = KeyguardState.OCCLUDED,
+                            to = KeyguardState.LOCKSCREEN,
+                            transitionState = TransitionState.STARTED,
+                    )
+            yield()
+
+            verify(animations, times(2)).doze(0f)
+
+            job.cancel()
+        }
+
+    @Test
     fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
@@ -379,6 +400,27 @@
 
             verify(animations, never()).doze(1f)
 
+                job.cancel()
+            }
+
+    @Test
+    fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
+                    .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToLockscreenTransition(this)
+            transitionStep.value =
+                    TransitionStep(
+                            from = KeyguardState.AOD,
+                            to = KeyguardState.LOCKSCREEN,
+                            transitionState = TransitionState.STARTED,
+                    )
+            yield()
+
+            verify(animations, never()).doze(0f)
+
             job.cancel()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c20367e..d267ad4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1274,6 +1274,28 @@
     }
 
     @Test
+    public void delayedFaceSensorLocationChangesAddsFaceScanningOverlay() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);
+        mScreenDecorations.start();
+        verifyFaceScanningViewExists(false); // face scanning view not added yet
+
+        // WHEN the sensor location is updated
+        mFaceScanningProviders = new ArrayList<>();
+        mFaceScanningProviders.add(mFaceScanningDecorProvider);
+        when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
+        when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
+        final Point location = new Point();
+        mFakeFacePropertyRepository.setSensorLocation(location);
+        mScreenDecorations.onFaceSensorLocationChanged(location);
+        mExecutor.runAllReady();
+
+        // THEN the face scanning view is added
+        verifyFaceScanningViewExists(true);
+    }
+
+    @Test
     public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
         mPrivacyDotShowingListener.onPrivacyDotShown(null);
         mPrivacyDotShowingListener.onPrivacyDotHidden(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 69cd592..e076420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -181,7 +181,7 @@
             throws RemoteException {
         enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -220,7 +220,7 @@
                 mWaitAnimationDuration, /* targetScale= */ 1.0f,
                 DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
 
-        verify(mSpyController).enableWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X,
+        verify(mSpyController).updateWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X,
                 DEFAULT_CENTER_Y, 0f, 0f);
         verify(mAnimationCallback).onResult(true);
     }
@@ -244,7 +244,7 @@
             advanceTimeBy(mWaitAnimationDuration);
         });
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -299,7 +299,7 @@
             advanceTimeBy(mWaitAnimationDuration);
         });
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -341,7 +341,7 @@
             advanceTimeBy(mWaitAnimationDuration);
         });
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -377,7 +377,7 @@
             advanceTimeBy(mWaitAnimationDuration);
         });
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -439,7 +439,7 @@
         enableWindowMagnificationAndWaitAnimating(
                 mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2);
 
-        verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
+        verify(mSpyController, never()).updateWindowMagnificationInternal(anyFloat(), anyFloat(),
                 anyFloat());
         verify(mAnimationCallback).onResult(false);
         verify(mAnimationCallback2).onResult(true);
@@ -479,7 +479,7 @@
         // Verify the method is called in
         // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
         // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
-        verify(mSpyController, times(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, times(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -526,7 +526,7 @@
         enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
                 Float.NaN, Float.NaN, mAnimationCallback2);
 
-        verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
+        verify(mSpyController, never()).updateWindowMagnificationInternal(anyFloat(), anyFloat(),
                 anyFloat());
         verify(mSpyController, never()).deleteWindowMagnification();
         verify(mAnimationCallback).onResult(false);
@@ -551,7 +551,7 @@
             advanceTimeBy(mWaitAnimationDuration);
         });
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -720,7 +720,7 @@
 
         enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
-        verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
+        verify(mSpyController, never()).updateWindowMagnificationInternal(anyFloat(), anyFloat(),
                 anyFloat());
         verify(mAnimationCallback).onResult(true);
     }
@@ -733,7 +733,7 @@
         resetMockObjects();
         deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
 
-        verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -790,7 +790,7 @@
         // Verify the method is called in
         // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
         // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
-        verify(mSpyController, times(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, times(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -835,7 +835,7 @@
         // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
         // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
         // the final duration time.
-        verify(mSpyController, times(2)).enableWindowMagnificationInternal(
+        verify(mSpyController, times(2)).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -1040,17 +1040,17 @@
         }
 
         @Override
-        void enableWindowMagnificationInternal(float scale, float centerX, float centerY) {
-            super.enableWindowMagnificationInternal(scale, centerX, centerY);
-            mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY);
+        void updateWindowMagnificationInternal(float scale, float centerX, float centerY) {
+            super.updateWindowMagnificationInternal(scale, centerX, centerY);
+            mSpyController.updateWindowMagnificationInternal(scale, centerX, centerY);
         }
 
         @Override
-        void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+        void updateWindowMagnificationInternal(float scale, float centerX, float centerY,
                 float magnificationOffsetFrameRatioX, float magnificationOffsetFrameRatioY) {
-            super.enableWindowMagnificationInternal(scale, centerX, centerY,
+            super.updateWindowMagnificationInternal(scale, centerX, centerY,
                     magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY);
-            mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY,
+            mSpyController.updateWindowMagnificationInternal(scale, centerX, centerY,
                     magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY);
         }
 
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 6f285fb..cb42078 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -273,7 +273,7 @@
     @Test
     public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -341,7 +341,7 @@
     @Test
     public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
         // Wait for Rects updated.
@@ -358,7 +358,7 @@
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -373,7 +373,7 @@
     @Test
     public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN,
                         Float.NaN));
 
@@ -391,7 +391,7 @@
         setSystemGestureInsets();
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     bounds.bottom);
         });
         ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
@@ -407,7 +407,7 @@
     @Test
     public void deleteWindowMagnification_notifySourceBoundsChanged() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN,
                         Float.NaN));
 
@@ -423,7 +423,7 @@
     @Test
     public void moveMagnifier_schedulesFrame() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.moveWindowMagnifier(100f, 100f);
         });
@@ -506,7 +506,7 @@
     @Test
     public void setScale_enabled_expectedValueAndUpdateStateDescription() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
                         Float.NaN, Float.NaN));
 
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
@@ -539,7 +539,7 @@
         final float displayWidth = windowBounds.width();
         final PointF magnifiedCenter = new PointF(center, center + 5f);
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                     magnifiedCenter.x, magnifiedCenter.y);
             // Get the center again in case the center we set is out of screen.
             magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
@@ -582,7 +582,7 @@
         final float expectedRatio = 0.5f;
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -621,8 +621,8 @@
                         preferredWindowSize.toString())
                 .commit();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController
-                    .enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
         });
 
         // Change screen density and size to trigger restoring the preferred window size
@@ -649,7 +649,7 @@
     @Test
     public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
         final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
@@ -674,7 +674,7 @@
     @Test
     public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             Mockito.reset(mWindowManager);
             Mockito.reset(mMirrorWindowControl);
@@ -703,7 +703,7 @@
     @Test
     public void initializeA11yNode_enabled_expectedValues() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
                     Float.NaN);
         });
         final View mirrorView = mWindowManager.getAttachedView();
@@ -727,7 +727,7 @@
     public void performA11yActions_visible_expectedResults() {
         final int displayId = mContext.getDisplayId();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(1.5f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
                     Float.NaN);
         });
 
@@ -761,7 +761,7 @@
     public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
         final int displayId = mContext.getDisplayId();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
                     Float.NaN);
         });
 
@@ -774,7 +774,7 @@
     @Test
     public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
@@ -812,7 +812,7 @@
                 /* pbase= */ 1);
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -852,7 +852,7 @@
                 /* pbase= */ 1);
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -888,7 +888,7 @@
         final int startingHeight = windowBounds.height();
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -908,7 +908,7 @@
         final int startingHeight = windowBounds.height();
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -931,7 +931,7 @@
                 /* base= */ 1,
                 /* pbase= */ 1);
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -971,7 +971,7 @@
                 /* pbase= */ 1);
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1007,7 +1007,7 @@
         final int startingSize = mMinWindowSize;
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1027,7 +1027,7 @@
         final int startingSize = mMinWindowSize;
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1043,7 +1043,7 @@
     @Test
     public void enableWindowMagnification_hasA11yWindowTitle() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -1054,12 +1054,12 @@
     @Test
     public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
                     Float.NaN);
         });
 
@@ -1078,7 +1078,7 @@
         final int newRotation = simulateRotateTheDevice();
 
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         assertEquals(newRotation, mWindowMagnificationController.mRotation);
@@ -1087,7 +1087,7 @@
     @Test
     public void enableWindowMagnification_registerComponentCallback() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN,
                         Float.NaN));
 
@@ -1098,7 +1098,7 @@
     public void onLocaleChanged_enabled_updateA11yWindowTitle() {
         final String newA11yWindowTitle = "new a11y window title";
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
         final TestableResources testableResources = getContext().getOrCreateTestableResources();
@@ -1116,7 +1116,7 @@
     @Test
     public void onSingleTap_enabled_scaleAnimates() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -1143,7 +1143,7 @@
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         setSystemGestureInsets();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -1161,7 +1161,7 @@
         setSystemGestureInsets();
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
 
@@ -1197,7 +1197,7 @@
         setSystemGestureInsets();
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
 
@@ -1232,7 +1232,7 @@
         final int  expectedWindowHeight = minimumWindowSize;
         final int  expectedWindowWidth = minimumWindowSize;
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1259,7 +1259,7 @@
         final AtomicInteger actualWindowWidth = new AtomicInteger();
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                     Float.NaN, Float.NaN);
             actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
             actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
@@ -1274,7 +1274,7 @@
         final int minimumWindowSize = mResources.getDimensionPixelSize(
                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1294,7 +1294,7 @@
     public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1323,7 +1323,7 @@
 
         mInstrumentation.runOnMainSync(
                 () ->
-                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
                                 Float.NaN, Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1348,7 +1348,7 @@
 
         mInstrumentation.runOnMainSync(
                 () ->
-                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
                                 Float.NaN, Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1382,7 +1382,7 @@
 
         mInstrumentation.runOnMainSync(
                 () ->
-                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
                                 Float.NaN, Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1408,7 +1408,7 @@
                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger magnificationCenterX = new AtomicInteger();
@@ -1429,7 +1429,7 @@
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
                             1.5f, bounds.centerX(), bounds.centerY());
                 });
         View dragButton = getInternalView(R.id.drag_handle);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index e9d36b8..a88654b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -282,7 +282,7 @@
     @Test
     public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -351,7 +351,7 @@
     @Test
     public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
         // Wait for Rects updated.
@@ -368,7 +368,7 @@
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -383,7 +383,7 @@
     @Test
     public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN,
                         Float.NaN));
 
@@ -401,7 +401,7 @@
         setSystemGestureInsets();
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     bounds.bottom);
         });
         ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
@@ -417,7 +417,7 @@
     @Test
     public void deleteWindowMagnification_notifySourceBoundsChanged() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN,
                         Float.NaN));
 
@@ -433,7 +433,7 @@
     @Test
     public void moveMagnifier_schedulesFrame() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -520,7 +520,7 @@
     @Test
     public void setScale_enabled_expectedValueAndUpdateStateDescription() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
                         Float.NaN, Float.NaN));
 
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
@@ -553,7 +553,7 @@
         final float displayWidth = windowBounds.width();
         final PointF magnifiedCenter = new PointF(center, center + 5f);
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                     magnifiedCenter.x, magnifiedCenter.y);
             // Get the center again in case the center we set is out of screen.
             magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
@@ -596,7 +596,7 @@
         final float expectedRatio = 0.5f;
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -635,8 +635,8 @@
                         preferredWindowSize.toString())
                 .commit();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController
-                    .enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
         });
 
         // Screen density and size change
@@ -663,7 +663,7 @@
     @Test
     public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
         final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
@@ -688,7 +688,7 @@
     @Test
     public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             Mockito.reset(mWindowManager);
             Mockito.reset(mMirrorWindowControl);
@@ -717,7 +717,7 @@
     @Test
     public void initializeA11yNode_enabled_expectedValues() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
                     Float.NaN);
         });
         final View mirrorView = mSurfaceControlViewHost.getView();
@@ -741,7 +741,7 @@
     public void performA11yActions_visible_expectedResults() {
         final int displayId = mContext.getDisplayId();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(1.5f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
                     Float.NaN);
         });
 
@@ -775,7 +775,7 @@
     public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
         final int displayId = mContext.getDisplayId();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
                     Float.NaN);
         });
 
@@ -788,7 +788,7 @@
     @Test
     public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
@@ -829,7 +829,7 @@
                 /* pbase= */ 1);
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -871,7 +871,7 @@
                 /* pbase= */ 1);
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -909,7 +909,7 @@
         final int startingHeight = windowBounds.height();
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -929,7 +929,7 @@
         final int startingHeight = windowBounds.height();
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -952,7 +952,7 @@
                 /* base= */ 1,
                 /* pbase= */ 1);
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -994,7 +994,7 @@
                 /* pbase= */ 1);
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1032,7 +1032,7 @@
         final int startingSize = mMinWindowSize;
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1052,7 +1052,7 @@
         final int startingSize = mMinWindowSize;
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1068,7 +1068,7 @@
     @Test
     public void enableWindowMagnification_hasA11yWindowTitle() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -1079,12 +1079,12 @@
     @Test
     public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
                     Float.NaN);
         });
 
@@ -1103,7 +1103,7 @@
         final int newRotation = simulateRotateTheDevice();
 
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         assertEquals(newRotation, mWindowMagnificationController.mRotation);
@@ -1112,7 +1112,7 @@
     @Test
     public void enableWindowMagnification_registerComponentCallback() {
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN,
                         Float.NaN));
 
@@ -1123,7 +1123,7 @@
     public void onLocaleChanged_enabled_updateA11yWindowTitle() {
         final String newA11yWindowTitle = "new a11y window title";
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
         final TestableResources testableResources = getContext().getOrCreateTestableResources();
@@ -1141,7 +1141,7 @@
     @Test
     public void onSingleTap_enabled_scaleAnimates() {
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -1168,7 +1168,7 @@
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         setSystemGestureInsets();
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -1186,7 +1186,7 @@
         setSystemGestureInsets();
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
         // Wait for Region updated.
@@ -1215,7 +1215,7 @@
         setSystemGestureInsets();
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
         // Wait for Region updated.
@@ -1244,7 +1244,7 @@
         final int  expectedWindowHeight = minimumWindowSize;
         final int  expectedWindowWidth = minimumWindowSize;
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1271,7 +1271,7 @@
         final AtomicInteger actualWindowWidth = new AtomicInteger();
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                     Float.NaN, Float.NaN);
             actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
             actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
@@ -1286,7 +1286,7 @@
         final int minimumWindowSize = mResources.getDimensionPixelSize(
                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1306,7 +1306,7 @@
     public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1335,7 +1335,7 @@
 
         mInstrumentation.runOnMainSync(
                 () ->
-                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
                                 Float.NaN, Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1362,7 +1362,7 @@
 
         mInstrumentation.runOnMainSync(
                 () ->
-                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
                                 Float.NaN, Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1398,7 +1398,7 @@
 
         mInstrumentation.runOnMainSync(
                 () ->
-                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
                                 Float.NaN, Float.NaN, Float.NaN));
 
         final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1426,7 +1426,7 @@
                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
         final AtomicInteger magnificationCenterX = new AtomicInteger();
@@ -1447,7 +1447,7 @@
         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
                             1.5f, bounds.centerX(), bounds.centerY());
                 });
         View dragButton = getInternalView(R.id.drag_handle);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index b0f0363..1576457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -43,10 +43,10 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemType;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
index 95d5597..d16db65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
@@ -27,7 +27,7 @@
 
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 33a6010..8f3fed7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.biometrics
 
+import android.app.ActivityTaskManager
 import android.app.admin.DevicePolicyManager
 import android.content.pm.PackageManager
 import android.hardware.biometrics.BiometricAuthenticator
@@ -44,9 +45,12 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
@@ -120,10 +124,12 @@
     lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock
     private lateinit var packageManager: PackageManager
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
 
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val biometricPromptRepository = FakePromptRepository()
+    private val biometricStatusRepository = FakeBiometricStatusRepository()
     private val fingerprintRepository = FakeFingerprintPropertyRepository()
     private val displayStateRepository = FakeDisplayStateRepository()
     private val credentialInteractor = FakeCredentialInteractor()
@@ -143,6 +149,7 @@
     private lateinit var displayRepository: FakeDisplayRepository
     private lateinit var displayStateInteractor: DisplayStateInteractor
     private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
 
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
     private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
@@ -168,6 +175,8 @@
                         selectedUserInteractor,
                         testScope.backgroundScope,
                 )
+        biometricStatusInteractor =
+                BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
         // Set up default logo icon
         whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
         context.setMockPackageManager(packageManager)
@@ -645,6 +654,7 @@
             promptSelectorInteractor,
             context,
             udfpsOverlayInteractor,
+            biometricStatusInteractor,
             udfpsUtils
         ),
         { credentialViewModel },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
index bf6caad..31bdde2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -1,13 +1,11 @@
 package com.android.systemui.biometrics.domain.interactor
 
-import android.view.Display
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.display.data.repository.display
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -101,14 +99,11 @@
     fun isDefaultDisplayOffChanges() =
         testScope.runTest {
             val isDefaultDisplayOff by collectLastValue(interactor.isDefaultDisplayOff)
-            runCurrent()
 
-            displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
-            displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+            displayRepository.setDefaultDisplayOff(true)
             assertThat(isDefaultDisplayOff).isTrue()
 
-            displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
-            displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+            displayRepository.setDefaultDisplayOff(false)
             assertThat(isDefaultDisplayOff).isFalse()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 5b0df5d..a6c7f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.app.ActivityTaskManager
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.graphics.Point
 import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.BiometricFingerprintConstants
 import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
 import android.hardware.biometrics.PromptContentItemBulletedText
 import android.hardware.biometrics.PromptContentView
@@ -40,9 +42,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -51,6 +56,7 @@
 import com.android.systemui.biometrics.extractAuthenticatorTypes
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -59,6 +65,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -102,6 +109,7 @@
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
     @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
@@ -115,9 +123,11 @@
     private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
     private lateinit var promptRepository: FakePromptRepository
     private lateinit var displayStateRepository: FakeDisplayStateRepository
+    private lateinit var biometricStatusRepository: FakeBiometricStatusRepository
     private lateinit var displayRepository: FakeDisplayRepository
     private lateinit var displayStateInteractor: DisplayStateInteractor
     private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
@@ -157,6 +167,9 @@
                 selectedUserInteractor,
                 testScope.backgroundScope
             )
+        biometricStatusRepository = FakeBiometricStatusRepository()
+        biometricStatusInteractor =
+            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
         selector =
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
         selector.resetPrompt()
@@ -178,6 +191,7 @@
                 selector,
                 mContext,
                 udfpsOverlayInteractor,
+                biometricStatusInteractor,
                 udfpsUtils
             )
         iconViewModel = viewModel.iconViewModel
@@ -1053,8 +1067,7 @@
     fun auto_confirm_authentication_when_finger_down() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
 
-        // No icon button when face only, can't confirm before auth
-        if (!testCase.isFaceOnly) {
+        if (testCase.isCoex) {
             viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
         }
         viewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -1069,14 +1082,18 @@
         assertThat(canTryAgain).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
 
-        if (testCase.isFaceOnly && expectConfirmation) {
-            assertThat(size).isEqualTo(PromptSize.MEDIUM)
-            assertButtonsVisible(
-                cancel = true,
-                confirm = true,
-            )
+        if (expectConfirmation) {
+            if (testCase.isFaceOnly) {
+                assertThat(size).isEqualTo(PromptSize.MEDIUM)
+                assertButtonsVisible(
+                    cancel = true,
+                    confirm = true,
+                )
 
-            viewModel.confirmAuthenticated()
+                viewModel.confirmAuthenticated()
+            } else if (testCase.isCoex) {
+                assertThat(authenticated?.isAuthenticatedAndConfirmed).isTrue()
+            }
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertButtonsVisible()
         }
@@ -1086,8 +1103,7 @@
     fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
 
-        // No icon button when face only, can't confirm before auth
-        if (!testCase.isFaceOnly) {
+        if (testCase.isCoex) {
             viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
             viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
         }
@@ -1413,6 +1429,12 @@
             packageName = packageName,
         )
 
+        biometricStatusRepository.setFingerprintAcquiredStatus(
+            AcquiredFingerprintAuthenticationStatus(
+                AuthenticationReason.BiometricPromptAuthentication,
+                BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN
+            )
+        )
         // put the view model in the initial authenticating state, unless explicitly skipped
         val startMode =
             when {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 1acb203..238a76e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -33,6 +33,7 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.settingslib.Utils
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -351,6 +352,7 @@
     @Test
     fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() {
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
             setupTestConfiguration(
                 DeviceConfig.X_ALIGNED,
                 rotation = DisplayRotation.ROTATION_0,
@@ -392,6 +394,7 @@
     @Test
     fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() {
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
             setupTestConfiguration(
                 DeviceConfig.Y_ALIGNED,
                 rotation = DisplayRotation.ROTATION_0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
index 036d3c8..4949716 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
index 3119284..85e2a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index 479e62d..a8f82ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 17b6127..12dfe97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
-import android.content.Context
 import android.graphics.drawable.Drawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -121,23 +120,18 @@
                 sysuiDialogFactory
             )
 
-        whenever(
-                sysuiDialogFactory.create(
-                    any(SystemUIDialog.Delegate::class.java)
-                )
+        whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer {
+            SystemUIDialog(
+                mContext,
+                0,
+                SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                dialogManager,
+                sysuiState,
+                fakeBroadcastDispatcher,
+                dialogTransitionAnimator,
+                it.getArgument(0)
             )
-            .thenAnswer {
-                SystemUIDialog(
-                    mContext,
-                    0,
-                    SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
-                    dialogManager,
-                    sysuiState,
-                    fakeBroadcastDispatcher,
-                    dialogTransitionAnimator,
-                    it.getArgument(0)
-                )
-            }
+        }
 
         icon = Pair(drawable, DEVICE_NAME)
         deviceItem =
@@ -163,6 +157,7 @@
         assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
         assertThat(recyclerView.adapter).isNotNull()
         assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+        dialog.dismiss()
     }
 
     @Test
@@ -184,6 +179,7 @@
             assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
             assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
             assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+            dialog.dismiss()
         }
     }
 
@@ -259,6 +255,7 @@
             assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
             assertThat(adapter.itemCount).isEqualTo(1)
             assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
+            dialog.dismiss()
         }
     }
 
@@ -283,6 +280,7 @@
             dialog.show()
             assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
                 .isEqualTo(cachedHeight)
+            dialog.dismiss()
         }
     }
 
@@ -306,6 +304,7 @@
             dialog.show()
             assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
                 .isGreaterThan(MATCH_PARENT)
+            dialog.dismiss()
         }
     }
 
@@ -331,6 +330,7 @@
                     dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
                 )
                 .isEqualTo(GONE)
+            dialog.dismiss()
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
index da8f60a..4aa6209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index c8a2aa6..6d99c5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index a8cd8c8..28cbcb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothDevice
 import android.content.pm.PackageInfo
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index ddf0b9a..eb735cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 45d20dc..8e5ddc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -27,6 +27,7 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import androidx.test.filters.SmallTest;
@@ -202,6 +203,36 @@
     }
 
     @Test
+    public void testPassThroughEnterKeyEvent() {
+        KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
+                0, 0, 0, 0, 0, 0, 0, "");
+        KeyEvent enterUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0,
+                0, 0, 0, 0, 0, 0, "");
+
+        mFalsingCollector.onKeyEvent(enterDown);
+        verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+        mFalsingCollector.onKeyEvent(enterUp);
+        verify(mFalsingDataProvider, times(1)).onKeyEvent(enterUp);
+    }
+
+    @Test
+    public void testAvoidAKeyEvent() {
+        // Arbitrarily chose the "A" key, as it is not currently allowlisted. If this key is
+        // allowlisted in the future, please choose another key that will not be collected.
+        KeyEvent aKeyDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
+                0, 0, 0, 0, 0, 0, 0, "");
+        KeyEvent aKeyUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0,
+                0, 0, 0, 0, 0, 0, "");
+
+        mFalsingCollector.onKeyEvent(aKeyDown);
+        verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+        mFalsingCollector.onKeyEvent(aKeyUp);
+        verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+    }
+
+    @Test
     public void testPassThroughGesture() {
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 0353f87..057b0a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -27,6 +27,7 @@
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import androidx.test.filters.SmallTest;
@@ -282,6 +283,22 @@
     }
 
     @Test
+    public void test_isFromKeyboard_disallowedKey_false() {
+        KeyEvent eventDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0,
+                0, 0, 0, 0, 0, 0, "");
+        KeyEvent eventUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+                0, 0, 0, 0, 0, "");
+
+        //events have not come in yet
+        assertThat(mDataProvider.isFromKeyboard()).isFalse();
+
+        mDataProvider.onKeyEvent(eventDown);
+        mDataProvider.onKeyEvent(eventUp);
+        assertThat(mDataProvider.isFromKeyboard()).isTrue();
+        mDataProvider.onSessionEnd();
+    }
+
+    @Test
     public void test_IsFromTrackpad() {
         MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
new file mode 100644
index 0000000..ad7afa3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.classifier;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TimeLimitedInputEventBufferTest extends SysuiTestCase {
+
+    private static final long MAX_AGE_MS = 100;
+
+    private TimeLimitedInputEventBuffer<InputEvent> mBuffer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBuffer = new TimeLimitedInputEventBuffer<>(MAX_AGE_MS);
+    }
+
+    @After
+    public void tearDown() {
+        for (InputEvent inputEvent : mBuffer) {
+            inputEvent.recycle();
+        }
+        mBuffer.clear();
+    }
+
+    @Test
+    public void testMotionEventAllEventsRetained() {
+        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+        mBuffer.add(eventC);
+        mBuffer.add(eventD);
+
+        assertThat(mBuffer.size(), is(4));
+    }
+
+    @Test
+    public void testMotionEventOlderEventsRemoved() {
+        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventC = MotionEvent.obtain(
+                0, MAX_AGE_MS + 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventD = MotionEvent.obtain(
+                0, MAX_AGE_MS + 2, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+        assertThat(mBuffer.size(), is(2));
+
+        mBuffer.add(eventC);
+        mBuffer.add(eventD);
+        assertThat(mBuffer.size(), is(2));
+
+        assertThat(mBuffer.get(0), is(eventC));
+        assertThat(mBuffer.get(1), is(eventD));
+    }
+
+    @Test
+    public void testKeyEventAllEventsRetained() {
+        KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+                0, 0, 0, 0, 0, "");
+        KeyEvent eventB = KeyEvent.obtain(0, 3, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+                0, 0, 0, "");
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+
+        assertThat(mBuffer.size(), is(2));
+    }
+
+    @Test
+    public void testKeyEventOlderEventsRemoved() {
+        KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+                0, 0, 0, 0, 0, "");
+        KeyEvent eventB = KeyEvent.obtain(0, 1, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+                0, 0, 0, "");
+        KeyEvent eventC = KeyEvent.obtain(0, MAX_AGE_MS + 1, MotionEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_A, 0, 0, 0, 0, 0, 0, 0, "");
+        KeyEvent eventD = KeyEvent.obtain(0, MAX_AGE_MS + 2, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A,
+                0, 0, 0, 0, 0, 0, 0, "");
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+        assertThat(mBuffer.size(), is(2));
+
+        mBuffer.add(eventC);
+        mBuffer.add(eventD);
+        assertThat(mBuffer.size(), is(2));
+
+        assertThat(mBuffer.get(0), is(eventC));
+        assertThat(mBuffer.get(1), is(eventD));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java
deleted file mode 100644
index 901196f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java
+++ /dev/null
@@ -1,92 +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.classifier;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-import android.testing.AndroidTestingRunner;
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
-
-    private static final long MAX_AGE_MS = 100;
-
-    private TimeLimitedMotionEventBuffer mBuffer;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mBuffer = new TimeLimitedMotionEventBuffer(MAX_AGE_MS);
-    }
-
-    @After
-    public void tearDown() {
-        for (MotionEvent motionEvent : mBuffer) {
-            motionEvent.recycle();
-        }
-        mBuffer.clear();
-    }
-
-    @Test
-    public void testAllEventsRetained() {
-        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
-        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
-
-        mBuffer.add(eventA);
-        mBuffer.add(eventB);
-        mBuffer.add(eventC);
-        mBuffer.add(eventD);
-
-        assertThat(mBuffer.size(), is(4));
-    }
-
-    @Test
-    public void testOlderEventsRemoved() {
-        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
-        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        MotionEvent eventC = MotionEvent.obtain(
-                0, MAX_AGE_MS + 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        MotionEvent eventD = MotionEvent.obtain(
-                0, MAX_AGE_MS + 2, MotionEvent.ACTION_UP, 0, 0, 0);
-
-        mBuffer.add(eventA);
-        mBuffer.add(eventB);
-        assertThat(mBuffer.size(), is(2));
-
-        mBuffer.add(eventC);
-        mBuffer.add(eventD);
-        assertThat(mBuffer.size(), is(2));
-
-        assertThat(mBuffer.get(0), is(eventC));
-        assertThat(mBuffer.get(1), is(eventD));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 806930d..68d49c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -22,6 +22,7 @@
 import android.testing.TestableLooper
 import android.view.Display
 import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
@@ -427,6 +428,35 @@
             assertThat(display!!.type).isEqualTo(TYPE_EXTERNAL)
         }
 
+    @Test
+    fun defaultDisplayOff_changes() =
+        testScope.runTest {
+            val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
+            setDisplays(
+                listOf(
+                    display(
+                        type = TYPE_INTERNAL,
+                        id = Display.DEFAULT_DISPLAY,
+                        state = Display.STATE_OFF
+                    )
+                )
+            )
+            displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+            assertThat(defaultDisplayOff).isTrue()
+
+            setDisplays(
+                listOf(
+                    display(
+                        type = TYPE_INTERNAL,
+                        id = Display.DEFAULT_DISPLAY,
+                        state = Display.STATE_ON
+                    )
+                )
+            )
+            displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+            assertThat(defaultDisplayOff).isFalse()
+        }
+
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
 
     // Wrapper to capture the displayListener.
@@ -436,6 +466,13 @@
         return flowValue
     }
 
+    // Wrapper to capture the displayListener.
+    private fun TestScope.latestDefaultDisplayOffFlowValue(): FlowValue<Boolean?> {
+        val flowValue = collectLastValue(displayRepository.defaultDisplayOff)
+        captureAddedRemovedListener()
+        return flowValue
+    }
+
     private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
         val flowValue = collectLastValue(displayRepository.pendingDisplay)
         captureAddedRemovedListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 695d3b2..ca403e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -47,6 +48,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -127,6 +129,12 @@
                 mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
+        // Using a fake package will cause routing operations to fail, so we intercept
+        // scanning-related operations.
+        mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
+        doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
+        doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 44798ea..2536078 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -257,6 +257,8 @@
             userId = userId,
             colorBackground = 0,
             isForegroundTask = isForegroundTask,
+            userType = RecentTask.UserType.STANDARD,
+            splitBounds = null,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
index 9b346d0..fa1c8f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
@@ -20,15 +20,10 @@
 import android.content.pm.ActivityInfo
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
 import androidx.test.filters.SmallTest
-import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.icons.FastBitmapDrawable
-import com.android.launcher3.icons.IconFactory
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shared.system.PackageManagerWrapper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -40,20 +35,17 @@
 
 @SmallTest
 @RunWith(JUnit4::class)
-class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
+class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() {
 
-    private val iconFactory: IconFactory = mock()
     private val packageManagerWrapper: PackageManagerWrapper = mock()
     private val packageManager: PackageManager = mock()
     private val dispatcher = Dispatchers.Unconfined
 
     private val appIconLoader =
-        IconLoaderLibAppIconLoader(
+        BasicPackageManagerAppIconLoader(
             backgroundDispatcher = dispatcher,
-            context = context,
             packageManagerWrapper = packageManagerWrapper,
             packageManager = packageManager,
-            iconFactoryProvider = { iconFactory }
         )
 
     @Test
@@ -70,12 +62,7 @@
     private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
         val activityInfo = mock<ActivityInfo>()
         whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
-        val rawIcon = mock<Drawable>()
-        whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)
-
-        val bitmapInfo = mock<BitmapInfo>()
-        whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
-        whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
+        whenever(activityInfo.loadIcon(packageManager)).thenReturn(icon)
     }
 
     private fun createIcon(): FastBitmapDrawable =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index b593def..6ac86f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -1,9 +1,15 @@
 package com.android.systemui.mediaprojection.appselector.data
 
 import android.app.ActivityManager.RecentTaskInfo
+import android.content.pm.UserInfo
+import android.os.UserManager
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.PRIVATE
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.STANDARD
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.WORK
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -17,6 +23,7 @@
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -25,12 +32,16 @@
     private val dispatcher = Dispatchers.Unconfined
     private val recentTasks: RecentTasks = mock()
     private val userTracker: UserTracker = mock()
+    private val userManager: UserManager = mock {
+        whenever(getUserInfo(anyInt())).thenReturn(mock())
+    }
     private val recentTaskListProvider =
         ShellRecentTaskListProvider(
             dispatcher,
             Runnable::run,
             Optional.of(recentTasks),
-            userTracker
+            userTracker,
+            userManager,
         )
 
     @Test
@@ -147,6 +158,22 @@
             .inOrder()
     }
 
+    @Test
+    fun loadRecentTasks_assignsCorrectUserType() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1, userId = 10, userType = STANDARD),
+            createSingleTask(taskId = 2, userId = 20, userType = WORK),
+            createSingleTask(taskId = 3, userId = 30, userType = CLONED),
+            createSingleTask(taskId = 4, userId = 40, userType = PRIVATE),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.userType })
+            .containsExactly(STANDARD, WORK, CLONED, PRIVATE)
+            .inOrder()
+    }
+
     @Suppress("UNCHECKED_CAST")
     private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
         whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
@@ -155,7 +182,10 @@
         }
     }
 
-    private fun createRecentTask(taskId: Int): RecentTask =
+    private fun createRecentTask(
+        taskId: Int,
+        userType: RecentTask.UserType = STANDARD
+    ): RecentTask =
         RecentTask(
             taskId = taskId,
             displayId = 0,
@@ -164,25 +194,43 @@
             baseIntentComponent = null,
             colorBackground = null,
             isForegroundTask = false,
+            userType = userType,
+            splitBounds = null
         )
 
-    private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo =
-        GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible))
+    private fun createSingleTask(
+        taskId: Int,
+        userId: Int = 0,
+        isVisible: Boolean = false,
+        userType: RecentTask.UserType = STANDARD,
+    ): GroupedRecentTaskInfo {
+        val userInfo =
+            mock<UserInfo> {
+                whenever(isCloneProfile).thenReturn(userType == CLONED)
+                whenever(isManagedProfile).thenReturn(userType == WORK)
+                whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
+            }
+        whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
+        return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+    }
 
     private fun createTaskPair(
         taskId1: Int,
+        userId1: Int = 0,
         taskId2: Int,
+        userId2: Int = 0,
         isVisible: Boolean = false
     ): GroupedRecentTaskInfo =
         GroupedRecentTaskInfo.forSplitTasks(
-            createTaskInfo(taskId1, isVisible),
-            createTaskInfo(taskId2, isVisible),
+            createTaskInfo(taskId1, userId1, isVisible),
+            createTaskInfo(taskId2, userId2, isVisible),
             null
         )
 
-    private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) =
+    private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) =
         RecentTaskInfo().apply {
             this.taskId = taskId
             this.isVisible = isVisible
+            this.userId = userId
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index ac41073..f4c5ccf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -18,22 +18,28 @@
 
 import android.app.ActivityOptions
 import android.app.IActivityTaskManager
+import android.graphics.Rect
 import android.os.Bundle
 import android.view.View
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX
+import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 
 @SmallTest
@@ -46,9 +52,10 @@
     private val taskViewSizeProvider = mock<TaskPreviewSizeProvider>()
     private val activityTaskManager = mock<IActivityTaskManager>()
     private val resultHandler = mock<MediaProjectionAppSelectorResultHandler>()
+    private val splitScreen = Optional.of(mock<SplitScreen>())
     private val bundleCaptor = ArgumentCaptor.forClass(Bundle::class.java)
 
-    private val task =
+    private val fullScreenTask =
         RecentTask(
             taskId = 123,
             displayId = 456,
@@ -56,7 +63,22 @@
             topActivityComponent = null,
             baseIntentComponent = null,
             colorBackground = null,
-            isForegroundTask = false
+            isForegroundTask = false,
+            userType = RecentTask.UserType.STANDARD,
+            splitBounds = null
+        )
+
+    private val splitScreenTask =
+        RecentTask(
+            taskId = 123,
+            displayId = 456,
+            userId = 789,
+            topActivityComponent = null,
+            baseIntentComponent = null,
+            colorBackground = null,
+            isForegroundTask = false,
+            userType = RecentTask.UserType.STANDARD,
+            splitBounds = SplitBounds(Rect(), Rect(), 0, 0, 0)
         )
 
     private val taskView =
@@ -69,61 +91,97 @@
             tasksAdapterFactory,
             taskViewSizeProvider,
             activityTaskManager,
-            resultHandler
+            resultHandler,
+            splitScreen,
         )
 
     @Test
-    fun onRecentAppClicked_taskWithSameIdIsStartedFromRecents() {
-        controller.onRecentAppClicked(task, taskView)
+    fun onRecentAppClicked_fullScreenTaskWithSameIdIsStartedFromRecents() {
+        controller.onRecentAppClicked(fullScreenTask, taskView)
 
-        verify(activityTaskManager).startActivityFromRecents(eq(task.taskId), any())
+        verify(activityTaskManager).startActivityFromRecents(eq(fullScreenTask.taskId), any())
+    }
+
+    @Test
+    fun onRecentAppClicked_splitScreenTaskWithSameIdIsStartedFromRecents() {
+        mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN)
+        controller.onRecentAppClicked(splitScreenTask, taskView)
+
+        verify(splitScreen.get())
+            .startTasks(
+                eq(splitScreenTask.taskId),
+                any(),
+                anyInt(),
+                any(),
+                anyInt(),
+                anyInt(),
+                any(),
+                any()
+            )
     }
 
     @Test
     fun onRecentAppClicked_launchDisplayIdIsSet() {
-        controller.onRecentAppClicked(task, taskView)
+        controller.onRecentAppClicked(fullScreenTask, taskView)
 
-        assertThat(getStartedTaskActivityOptions().launchDisplayId).isEqualTo(task.displayId)
+        assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).launchDisplayId)
+            .isEqualTo(fullScreenTask.displayId)
     }
 
     @Test
-    fun onRecentAppClicked_taskNotInForeground_usesScaleUpAnimation() {
-        controller.onRecentAppClicked(task, taskView)
+    fun onRecentAppClicked_fullScreenTaskNotInForeground_usesScaleUpAnimation() {
+        assertThat(fullScreenTask.isForegroundTask).isFalse()
+        controller.onRecentAppClicked(fullScreenTask, taskView)
 
-        assertThat(getStartedTaskActivityOptions().animationType)
+        assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType)
             .isEqualTo(ActivityOptions.ANIM_SCALE_UP)
     }
 
     @Test
-    fun onRecentAppClicked_taskInForeground_flagOff_usesScaleUpAnimation() {
+    fun onRecentAppClicked_fullScreenTaskInForeground_flagOff_usesScaleUpAnimation() {
         mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
 
-        controller.onRecentAppClicked(task, taskView)
+        controller.onRecentAppClicked(fullScreenTask, taskView)
 
-        assertThat(getStartedTaskActivityOptions().animationType)
+        assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType)
             .isEqualTo(ActivityOptions.ANIM_SCALE_UP)
     }
 
     @Test
-    fun onRecentAppClicked_taskInForeground_flagOn_usesDefaultAnimation() {
+    fun onRecentAppClicked_fullScreenTaskInForeground_flagOn_usesDefaultAnimation() {
         mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
-        val foregroundTask = task.copy(isForegroundTask = true)
+        assertForegroundTaskUsesDefaultCloseAnimation(fullScreenTask)
+    }
 
+    @Test
+    fun onRecentAppClicked_splitScreenTaskInForeground_flagOn_usesDefaultAnimation() {
+        mSetFlagsRule.enableFlags(
+            FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX,
+            FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN
+        )
+        assertForegroundTaskUsesDefaultCloseAnimation(splitScreenTask)
+    }
+
+    private fun assertForegroundTaskUsesDefaultCloseAnimation(task: RecentTask) {
+        val foregroundTask = task.copy(isForegroundTask = true)
         controller.onRecentAppClicked(foregroundTask, taskView)
 
         expect
-            .that(getStartedTaskActivityOptions().animationType)
+            .that(getStartedTaskActivityOptions(foregroundTask.taskId).animationType)
             .isEqualTo(ActivityOptions.ANIM_CUSTOM)
-        expect.that(getStartedTaskActivityOptions().overrideTaskTransition).isTrue()
         expect
-            .that(getStartedTaskActivityOptions().customExitResId)
+            .that(getStartedTaskActivityOptions(foregroundTask.taskId).overrideTaskTransition)
+            .isTrue()
+        expect
+            .that(getStartedTaskActivityOptions(foregroundTask.taskId).customExitResId)
             .isEqualTo(com.android.internal.R.anim.resolver_close_anim)
-        expect.that(getStartedTaskActivityOptions().customEnterResId).isEqualTo(0)
+        expect
+            .that(getStartedTaskActivityOptions(foregroundTask.taskId).customEnterResId)
+            .isEqualTo(0)
     }
 
-    private fun getStartedTaskActivityOptions(): ActivityOptions {
-        verify(activityTaskManager)
-            .startActivityFromRecents(eq(task.taskId), bundleCaptor.capture())
+    private fun getStartedTaskActivityOptions(taskId: Int): ActivityOptions {
+        verify(activityTaskManager).startActivityFromRecents(eq(taskId), bundleCaptor.capture())
         return ActivityOptions.fromBundle(bundleCaptor.value)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 82ee99a..830f08a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
 import com.android.systemui.statusbar.policy.BluetoothController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 9104f8e..6846c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -88,6 +88,7 @@
     private lateinit var dialog: SystemUIDialog
     private lateinit var factory: SystemUIDialog.Factory
     private lateinit var latch: CountDownLatch
+    private var issueRecordingState = IssueRecordingState()
 
     @Before
     fun setup() {
@@ -128,6 +129,7 @@
                     mediaProjectionMetricsLogger,
                     userFileManager,
                     screenCaptureDisabledDialogDelegate,
+                    issueRecordingState,
                 ) {
                     latch.countDown()
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
new file mode 100644
index 0000000..5e7d8fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.Window
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyBlocking
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActionExecutorTest : SysuiTestCase() {
+    private val scheduler = TestCoroutineScheduler()
+    private val mainDispatcher = StandardTestDispatcher(scheduler)
+    private val testScope = TestScope(mainDispatcher)
+
+    private val intentExecutor = mock<ActionIntentExecutor>()
+    private val window = mock<Window>()
+    private val view = mock<View>()
+    private val onDismiss = mock<(() -> Unit)>()
+    private val pendingIntent = mock<PendingIntent>()
+
+    private lateinit var actionExecutor: ActionExecutor
+
+    @Test
+    fun startSharedTransition_callsLaunchIntent() = runTest {
+        actionExecutor = createActionExecutor()
+
+        actionExecutor.startSharedTransition(Intent(Intent.ACTION_EDIT), UserHandle.CURRENT, true)
+        scheduler.advanceUntilIdle()
+
+        val intentCaptor = argumentCaptor<Intent>()
+        verifyBlocking(intentExecutor) {
+            launchIntent(capture(intentCaptor), eq(UserHandle.CURRENT), eq(true), any(), any())
+        }
+        assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+    }
+
+    @Test
+    fun sendPendingIntent_dismisses() = runTest {
+        actionExecutor = createActionExecutor()
+
+        actionExecutor.sendPendingIntent(pendingIntent)
+
+        verify(pendingIntent).send(any(Bundle::class.java))
+        verify(onDismiss).invoke()
+    }
+
+    private fun createActionExecutor(): ActionExecutor {
+        return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 0c32470..5e53fe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -64,7 +64,7 @@
             val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
             val userHandle = myUserHandle()
 
-            actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+            actionIntentExecutor.launchIntent(intent, userHandle, false, null, null)
             scheduler.advanceUntilIdle()
 
             verify(activityManagerWrapper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 4a5cf57..bde821b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -16,12 +16,11 @@
 
 package com.android.systemui.screenshot
 
-import android.app.ActivityOptions
-import android.app.ExitTransitionCoordinator
 import android.app.Notification
 import android.app.PendingIntent
 import android.content.Intent
 import android.net.Uri
+import android.os.Process
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.view.accessibility.AccessibilityManager
@@ -36,11 +35,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.Ignore
 import kotlin.test.Test
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertNotNull
 import org.junit.Before
@@ -49,32 +44,18 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyBlocking
 import org.mockito.kotlin.whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
-    private val scheduler = TestCoroutineScheduler()
-    private val mainDispatcher = StandardTestDispatcher(scheduler)
-    private val testScope = TestScope(mainDispatcher)
-
-    private val actionIntentExecutor = mock<ActionIntentExecutor>()
+    private val actionExecutor = mock<ActionExecutor>()
     private val accessibilityManager = mock<AccessibilityManager>()
     private val uiEventLogger = mock<UiEventLogger>()
     private val smartActionsProvider = mock<SmartActionsProvider>()
-    private val transition = mock<android.util.Pair<ActivityOptions, ExitTransitionCoordinator>>()
-    private val requestDismissal = mock<() -> Unit>()
 
     private val request = ScreenshotData.forTesting()
-    private val invalidResult = ScreenshotController.SavedImageData()
-    private val validResult =
-        ScreenshotController.SavedImageData().apply {
-            uri = Uri.EMPTY
-            owner = UserHandle.OWNER
-            subject = "Test"
-            imageTime = 0
-        }
+    private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
 
     private lateinit var viewModel: ScreenshotViewModel
     private lateinit var actionsProvider: ScreenshotActionsProvider
@@ -91,7 +72,7 @@
 
         assertNotNull(viewModel.previewAction.value)
         viewModel.previewAction.value!!.invoke()
-        verifyNoMoreInteractions(actionIntentExecutor)
+        verifyNoMoreInteractions(actionExecutor)
     }
 
     @Test
@@ -105,39 +86,24 @@
         assertThat(secondAction.onClicked).isNotNull()
         firstAction.onClicked!!.invoke()
         secondAction.onClicked!!.invoke()
-        verifyNoMoreInteractions(actionIntentExecutor)
+        verifyNoMoreInteractions(actionExecutor)
     }
 
     @Test
-    fun actionAccessed_withInvalidResult_doesNothing() {
-        actionsProvider = createActionsProvider()
-
-        actionsProvider.setCompletedScreenshot(invalidResult)
-        viewModel.previewAction.value!!.invoke()
-        viewModel.actions.value[1].onClicked!!.invoke()
-
-        verifyNoMoreInteractions(actionIntentExecutor)
-    }
-
-    @Test
-    @Ignore("b/332526567")
     fun actionAccessed_withResult_launchesIntent() = runTest {
         actionsProvider = createActionsProvider()
 
         actionsProvider.setCompletedScreenshot(validResult)
         viewModel.actions.value[0].onClicked!!.invoke()
-        scheduler.advanceUntilIdle()
 
         verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
         val intentCaptor = argumentCaptor<Intent>()
-        verifyBlocking(actionIntentExecutor) {
-            launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(true))
-        }
+        verify(actionExecutor)
+            .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true))
         assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
     }
 
     @Test
-    @Ignore("b/332526567")
     fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
         actionsProvider = createActionsProvider()
 
@@ -145,13 +111,11 @@
         viewModel.previewAction.value!!.invoke()
         viewModel.actions.value[1].onClicked!!.invoke()
         actionsProvider.setCompletedScreenshot(validResult)
-        scheduler.advanceUntilIdle()
 
         verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
         val intentCaptor = argumentCaptor<Intent>()
-        verifyBlocking(actionIntentExecutor) {
-            launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(false))
-        }
+        verify(actionExecutor)
+            .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false))
         assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
     }
 
@@ -172,7 +136,7 @@
             (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare)
         }
         whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer {
-            it.getArgument(0)
+            (it.getArgument(0) as Notification.Action).actionIntent
         }
         actionsProvider = createActionsProvider()
 
@@ -190,14 +154,11 @@
         return DefaultScreenshotActionsProvider(
             context,
             viewModel,
-            actionIntentExecutor,
             smartActionsProvider,
             uiEventLogger,
-            testScope,
             request,
             "testid",
-            { transition },
-            requestDismissal,
+            actionExecutor
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
new file mode 100644
index 0000000..d44e26c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.screenshot.ui.viewmodel
+
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ScreenshotViewModelTest {
+    private val accessibilityManager: AccessibilityManager = mock(AccessibilityManager::class.java)
+    private val appearance = ActionButtonAppearance(null, "Label", "Description")
+    private val onclick = {}
+
+    @Test
+    fun testAddAction() {
+        val viewModel = ScreenshotViewModel(accessibilityManager)
+
+        assertThat(viewModel.actions.value).isEmpty()
+
+        viewModel.addAction(appearance, onclick)
+
+        assertThat(viewModel.actions.value).hasSize(1)
+
+        val added = viewModel.actions.value[0]
+        assertThat(added.appearance).isEqualTo(appearance)
+        assertThat(added.onClicked).isEqualTo(onclick)
+    }
+
+    @Test
+    fun testRemoveAction() {
+        val viewModel = ScreenshotViewModel(accessibilityManager)
+        val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {})
+        val secondId = viewModel.addAction(appearance, onclick)
+
+        assertThat(viewModel.actions.value).hasSize(2)
+        assertThat(firstId).isNotEqualTo(secondId)
+
+        viewModel.removeAction(firstId)
+
+        assertThat(viewModel.actions.value).hasSize(1)
+
+        val remaining = viewModel.actions.value[0]
+        assertThat(remaining.appearance).isEqualTo(appearance)
+        assertThat(remaining.onClicked).isEqualTo(onclick)
+    }
+
+    @Test
+    fun testUpdateActionAppearance() {
+        val viewModel = ScreenshotViewModel(accessibilityManager)
+        val id = viewModel.addAction(appearance, onclick)
+        val otherAppearance = ActionButtonAppearance(null, "Other", "Other")
+
+        viewModel.updateActionAppearance(id, otherAppearance)
+
+        assertThat(viewModel.actions.value).hasSize(1)
+        val updated = viewModel.actions.value[0]
+        assertThat(updated.appearance).isEqualTo(otherAppearance)
+        assertThat(updated.onClicked).isEqualTo(onclick)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 25ba09a..6a22d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -84,7 +84,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(mirrorController.toggleSlider).thenReturn(mirror)
+        whenever(mirrorController.getToggleSlider()).thenReturn(mirror)
         whenever(motionEvent.copy()).thenReturn(motionEvent)
         whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
 
@@ -129,7 +129,7 @@
 
     @Test
     fun testNullMirrorNotTrackingTouch() {
-        whenever(mirrorController.toggleSlider).thenReturn(null)
+        whenever(mirrorController.getToggleSlider()).thenReturn(null)
 
         mController.setMirrorControllerAndMirror(mirrorController)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index dfbb699..2c0a15d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -69,7 +69,6 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -87,6 +86,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
+import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -138,6 +139,7 @@
     private val notificationLaunchAnimationInteractor =
         NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
 
+    private lateinit var falsingCollector: FalsingCollectorFake
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
     private lateinit var interactionEventHandler: InteractionEventHandler
@@ -170,11 +172,12 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
 
         testScope = TestScope()
+        falsingCollector = FalsingCollectorFake()
         fakeClock = FakeSystemClock()
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
-                FalsingCollectorFake(),
+                falsingCollector,
                 sysuiStatusBarStateController,
                 dockManager,
                 notificationShadeDepthController,
@@ -566,6 +569,13 @@
         verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
     }
 
+    @Test
+    fun forwardsCollectKeyEvent() {
+        val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
+        interactionEventHandler.collectKeyEvent(keyEvent)
+        assertEquals(keyEvent, falsingCollector.lastKeyEvent)
+    }
+
     companion object {
         private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
         private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33a838e..4b0b4b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -25,6 +25,7 @@
 import android.util.StatsEvent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertLogsWtf
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -133,8 +134,10 @@
         val pipeline: NotifPipeline = mock()
         whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
         val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
-        assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
-            .isEqualTo(StatsManager.PULL_SKIP)
+        assertLogsWtf {
+            assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+                .isEqualTo(StatsManager.PULL_SKIP)
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ed29665..2f153d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -25,6 +25,8 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -119,9 +121,9 @@
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelView;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -331,7 +333,10 @@
     private final DumpManager mDumpManager = new DumpManager();
     private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
 
-    private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+    private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+
+    private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
+            mKosmos.getBrightnessMirrorShowingInteractor();
 
     @Before
     public void setup() throws Exception {
@@ -553,7 +558,8 @@
                 mUserTracker,
                 () -> mFingerprintManager,
                 mActivityStarter,
-                mSceneContainerFlags
+                mSceneContainerFlags,
+                mBrightnessMirrorShowingInteractor
         );
         mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
         mCentralSurfaces.initShadeVisibilityListener();
@@ -1084,6 +1090,34 @@
         verify(mStatusBarWindowController).refreshStatusBarHeight();
     }
 
+    @Test
+    public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
+        mSceneContainerFlags.setEnabled(true);
+        mCentralSurfaces.registerCallbacks();
+
+        mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+        mTestScope.getTestScheduler().runCurrent();
+        verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+
+        mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
+        mTestScope.getTestScheduler().runCurrent();
+        ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
+        // The default is to call the one with the callback argument
+        verify(mScrimController).transitionTo(captor.capture(), any());
+        assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
+    }
+
+    @Test
+    public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
+        mSceneContainerFlags.setEnabled(false);
+        mCentralSurfaces.registerCallbacks();
+
+        mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+        mTestScope.getTestScheduler().runCurrent();
+        verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+        verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
+    }
+
     /**
      * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
      * to reconfigure the keyguard to reflect the requested showing/occluded states.
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 c1ef1ad..3e9006e 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
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.annotations.DisableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -46,6 +47,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.Clock;
@@ -157,6 +159,7 @@
     }
 
     @Test
+    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
     public void testHeaderUpdated() {
         mRow.setPinned(true);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
new file mode 100644
index 0000000..dddc712
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.unfold
+
+import android.os.PowerManager
+import android.os.SystemProperties
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class FoldLightRevealOverlayAnimationTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope: TestScope = kosmos.testScope
+    private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository
+    private val powerInteractor = kosmos.powerInteractor
+    private val fakeAnimationStatusRepository = kosmos.fakeAnimationStatusRepository
+    private val mockControllerFactory = kosmos.fullscreenLightRevealAnimationControllerFactory
+    private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController
+    private val mockFoldLockSettingAvailabilityProvider =
+        mock<FoldLockSettingAvailabilityProvider>()
+    private val onOverlayReady = mock<Runnable>()
+    private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable)
+            .thenReturn(true)
+        fakeAnimationStatusRepository.onAnimationStatusChanged(true)
+
+        foldLightRevealAnimation =
+            FoldLightRevealOverlayAnimation(
+                kosmos.testDispatcher,
+                fakeDeviceStateRepository,
+                powerInteractor,
+                testScope.backgroundScope,
+                fakeAnimationStatusRepository,
+                mockControllerFactory,
+                mockFoldLockSettingAvailabilityProvider
+            )
+        foldLightRevealAnimation.init()
+    }
+
+    @Test
+    fun foldToScreenOn_playFoldAnimation() =
+        testScope.runTest {
+            foldDeviceToScreenOff()
+            turnScreenOn()
+
+            verifyFoldAnimationPlayed()
+        }
+
+    @Test
+    fun foldToAod_doNotPlayFoldAnimation() =
+        testScope.runTest {
+            foldDeviceToScreenOff()
+            emitLastWakefulnessEventStartingToSleep()
+            advanceTimeBy(SHORT_DELAY_MS)
+            turnScreenOn()
+            advanceTimeBy(ANIMATION_DURATION)
+
+            verifyFoldAnimationDidNotPlay()
+        }
+
+    @Test
+    fun foldToScreenOff_doNotPlayFoldAnimation() =
+        testScope.runTest {
+            foldDeviceToScreenOff()
+            emitLastWakefulnessEventStartingToSleep()
+            advanceTimeBy(SHORT_DELAY_MS)
+            advanceTimeBy(ANIMATION_DURATION)
+
+            verifyFoldAnimationDidNotPlay()
+        }
+
+    @Test
+    fun foldToScreenOnWithDelay_doNotPlayFoldAnimation() =
+        testScope.runTest {
+            foldDeviceToScreenOff()
+            foldLightRevealAnimation.onScreenTurningOn {}
+            advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS)
+            powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            advanceTimeBy(SHORT_DELAY_MS)
+            advanceTimeBy(ANIMATION_DURATION)
+
+            verifyFoldAnimationDidNotPlay()
+        }
+
+    @Test
+    fun immediateUnfoldAfterFold_removeOverlayAfterCancellation() =
+        testScope.runTest {
+            foldDeviceToScreenOff()
+            foldLightRevealAnimation.onScreenTurningOn {}
+            advanceTimeBy(SHORT_DELAY_MS)
+            advanceTimeBy(ANIMATION_DURATION)
+            fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
+            advanceTimeBy(SHORT_DELAY_MS)
+            powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+
+            verifyOverlayWasRemoved()
+        }
+
+    @Test
+    fun foldToScreenOn_removeOverlayAfterCompletion() =
+        testScope.runTest {
+            foldDeviceToScreenOff()
+            turnScreenOn()
+            advanceTimeBy(ANIMATION_DURATION)
+
+            verifyOverlayWasRemoved()
+        }
+
+    @Test
+    fun unfold_immediatelyRunRunnable() =
+        testScope.runTest {
+            foldLightRevealAnimation.onScreenTurningOn(onOverlayReady)
+
+            verify(onOverlayReady).run()
+        }
+
+    private suspend fun TestScope.foldDeviceToScreenOff() {
+        fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
+        powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+        advanceTimeBy(SHORT_DELAY_MS)
+        fakeDeviceStateRepository.emit(DeviceState.FOLDED)
+        advanceTimeBy(SHORT_DELAY_MS)
+        powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF)
+        advanceTimeBy(SHORT_DELAY_MS)
+    }
+
+    private fun TestScope.turnScreenOn() {
+        foldLightRevealAnimation.onScreenTurningOn {}
+        advanceTimeBy(SHORT_DELAY_MS)
+        powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+        advanceTimeBy(SHORT_DELAY_MS)
+    }
+
+    private fun emitLastWakefulnessEventStartingToSleep() =
+        powerInteractor.setAsleepForTest(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+
+    private fun verifyFoldAnimationPlayed() =
+        verify(mockFullScreenController, atLeast(1)).updateRevealAmount(any())
+
+    private fun verifyFoldAnimationDidNotPlay() =
+        verify(mockFullScreenController, never()).updateRevealAmount(any())
+
+    private fun verifyOverlayWasRemoved() =
+        verify(mockFullScreenController, atLeast(1)).ensureOverlayRemoved()
+
+    private companion object {
+        const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
+        val ANIMATION_DURATION: Long
+            get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
+        const val SHORT_DELAY_MS = 50L
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
index 1125d41..0eba21a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.wallpapers;
 
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -32,6 +35,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -77,6 +81,7 @@
     private Executor mBackgroundExecutor;
 
     private int mColorsProcessed;
+    private int mLocalColorsProcessed;
     private int mMiniBitmapUpdatedCount;
     private int mActivatedCount;
     private int mDeactivatedCount;
@@ -93,6 +98,7 @@
 
     private void resetCounters() {
         mColorsProcessed = 0;
+        mLocalColorsProcessed = 0;
         mMiniBitmapUpdatedCount = 0;
         mActivatedCount = 0;
         mDeactivatedCount = 0;
@@ -112,10 +118,14 @@
                 new Object(),
                 new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
                     @Override
+                    public void onColorsProcessed() {
+                        mColorsProcessed++;
+                    }
+                    @Override
                     public void onColorsProcessed(List<RectF> regions,
                             List<WallpaperColors> colors) {
                         assertThat(regions.size()).isEqualTo(colors.size());
-                        mColorsProcessed += regions.size();
+                        mLocalColorsProcessed += regions.size();
                     }
 
                     @Override
@@ -148,8 +158,10 @@
                 .when(spyColorExtractor)
                 .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
 
-        doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
-                .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+        WallpaperColors colors = new WallpaperColors(
+                Color.valueOf(0), Color.valueOf(0), Color.valueOf(0));
+        doReturn(colors).when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+        doReturn(colors).when(spyColorExtractor).getWallpaperColors(any(Bitmap.class), anyFloat());
 
         return spyColorExtractor;
     }
@@ -244,7 +256,7 @@
 
             assertThat(mActivatedCount).isEqualTo(1);
             assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
-            assertThat(mColorsProcessed).isEqualTo(regions.size());
+            assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
 
             spyColorExtractor.removeLocalColorAreas(regions);
             assertThat(mDeactivatedCount).isEqualTo(1);
@@ -329,12 +341,69 @@
                 spyColorExtractor.onBitmapChanged(newBitmap);
                 assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
             }
-            assertThat(mColorsProcessed).isEqualTo(regions.size());
+            assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
         }
         spyColorExtractor.removeLocalColorAreas(regions);
         assertThat(mDeactivatedCount).isEqualTo(1);
     }
 
+    /**
+     * Test that after the bitmap changes, the colors are computed only if asked via onComputeColors
+     */
+    @Test
+    @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+    public void testRecomputeColors() {
+        resetCounters();
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+        spyColorExtractor.onBitmapChanged(bitmap);
+        assertThat(mColorsProcessed).isEqualTo(0);
+        spyColorExtractor.onComputeColors();
+        assertThat(mColorsProcessed).isEqualTo(1);
+    }
+
+    /**
+     * Test that after onComputeColors is called, the colors are computed once the bitmap is loaded
+     */
+    @Test
+    @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+    public void testRecomputeColorsBeforeBitmapLoaded() {
+        resetCounters();
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+        spyColorExtractor.onComputeColors();
+        spyColorExtractor.onBitmapChanged(bitmap);
+        assertThat(mColorsProcessed).isEqualTo(1);
+    }
+
+    /**
+     * Test that after the dim changes, the colors are computed if the bitmap is already loaded
+     */
+    @Test
+    @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+    public void testRecomputeColorsOnDimChanged() {
+        resetCounters();
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+        spyColorExtractor.onBitmapChanged(bitmap);
+        spyColorExtractor.onDimAmountChanged(0.5f);
+        assertThat(mColorsProcessed).isEqualTo(1);
+    }
+
+    /**
+     * Test that after the dim changes, the colors will be recomputed once the bitmap is loaded
+     */
+    @Test
+    @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+    public void testRecomputeColorsOnDimChangedBeforeBitmapLoaded() {
+        resetCounters();
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+        spyColorExtractor.onDimAmountChanged(0.3f);
+        spyColorExtractor.onBitmapChanged(bitmap);
+        assertThat(mColorsProcessed).isEqualTo(1);
+    }
+
     @Test
     public void testCleanUp() {
         resetCounters();
@@ -346,6 +415,6 @@
         assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
         spyColorExtractor.cleanUp();
         spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
-        assertThat(mColorsProcessed).isEqualTo(0);
+        assertThat(mLocalColorsProcessed).isEqualTo(0);
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
index 9cbe633..2ae6f542 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
@@ -30,6 +31,7 @@
         promptSelectorInteractor = promptSelectorInteractor,
         context = applicationContext,
         udfpsOverlayInteractor = udfpsOverlayInteractor,
+        biometricStatusInteractor = biometricStatusInteractor,
         udfpsUtils = udfpsUtils
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 0fc0a3c..6c3cf91 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -23,6 +23,8 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import org.mockito.Mockito.`when` as whenever
 
 /** Creates a mock display. */
@@ -69,12 +71,20 @@
     override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
         get() = pendingDisplayFlow
 
+    val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val defaultDisplayOff: Flow<Boolean>
+        get() = _defaultDisplayOff.asStateFlow()
+
     override val displayAdditionEvent: Flow<Display?>
         get() = displayAdditionEventFlow
 
     private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
     override val displayChangeEvent: Flow<Int> = _displayChangeEvent
     suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
+
+    fun setDefaultDisplayOff(defaultDisplayOff: Boolean) {
+        _defaultDisplayOff.value = defaultDisplayOff
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 1b23296..afc8f30 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.screenOffAnimationController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -106,6 +107,7 @@
     val sharedNotificationContainerInteractor by lazy {
         kosmos.sharedNotificationContainerInteractor
     }
+    val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
 
     init {
         kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 1ce2610..0de76c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
 import com.android.systemui.qs.footer.foregroundServicesRepository
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.security.data.repository.securityRepository
 import com.android.systemui.settings.userTracker
 import com.android.systemui.statusbar.policy.deviceProvisionedController
@@ -49,7 +48,6 @@
     QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
 }
 
-var Kosmos.newQSTileFactory by Fixture<NewQSTileFactory>()
 var Kosmos.qsTileFactory by Fixture<QSFactory>()
 
 val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index 9ef44c4..b870039 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.qs.external.customTileStatePersister
 import com.android.systemui.qs.external.tileLifecycleManagerFactory
-import com.android.systemui.qs.newQSTileFactory
 import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
@@ -29,6 +28,7 @@
 import com.android.systemui.qs.pipeline.shared.logging.qsLogger
 import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
 import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.qs.tiles.di.newQSTileFactory
 import com.android.systemui.settings.userTracker
 import com.android.systemui.user.data.repository.userRepository
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
new file mode 100644
index 0000000..5c4b390
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.di
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.mock
+import javax.inject.Provider
+import org.mockito.Mockito
+
+var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
+
+val Kosmos.newQSTileFactory by
+    Kosmos.Fixture {
+        NewQSTileFactory(
+            qSTileConfigProvider,
+            qsTileViewModelAdaperFactory,
+            newFactoryTileMap,
+            mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+            mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+        )
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
index 5d2b0ad2..1d57979 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.systemui.qs.tiles.viewmodel
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeQSTileConfigProvider by Kosmos.Fixture { FakeQSTileConfigProvider() }
+var Kosmos.qSTileConfigProvider: QSTileConfigProvider by Kosmos.Fixture { fakeQSTileConfigProvider }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
new file mode 100644
index 0000000..a908765
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.qs.tiles.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileViewModelAdaperFactory by
+    Kosmos.Fixture {
+        object : QSTileViewModelAdapter.Factory {
+            override fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter {
+                return QSTileViewModelAdapter(
+                    applicationCoroutineScope,
+                    mock(),
+                    qsTileViewModel,
+                )
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index df08e4a..a654d6f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.view.View
+import com.android.systemui.settings.brightness.MirrorController
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -41,6 +42,9 @@
     private val _navBarPadding = MutableStateFlow<Int>(0)
     val navBarPadding = _navBarPadding.asStateFlow()
 
+    var brightnessMirrorController: MirrorController? = null
+        private set
+
     override var isQsFullyCollapsed: Boolean = true
 
     override suspend fun inflate(context: Context) {
@@ -64,4 +68,8 @@
     override fun requestCloseCustomizer() {
         _customizing.value = false
     }
+
+    override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+        brightnessMirrorController = mirrorController
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
new file mode 100644
index 0000000..8b7e5d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.classifier.falsingManager
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.util.time.systemClock
+
+/** This factory creates empty mocks. */
+var Kosmos.brightnessSliderControllerFactory by
+    Kosmos.Fixture<BrightnessSliderController.Factory> {
+        BrightnessSliderController.Factory(
+            falsingManager,
+            uiEventLogger,
+            vibratorHelper,
+            systemClock,
+            activityStarter,
+        )
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
index 5d2b0ad2..6db4649 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.systemui.settings.brightness.data.repository
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.brightnessMirrorShowingRepository by
+    Kosmos.Fixture { BrightnessMirrorShowingRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
index 5d2b0ad2..8f6b829 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.systemui.settings.brightness.domain.interactor
 
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+
+val Kosmos.brightnessMirrorShowingInteractor by
+    Kosmos.Fixture { BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
new file mode 100644
index 0000000..8fb370c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightnessSliderControllerFactory
+
+val Kosmos.brightnessMirrorViewModel by
+    Kosmos.Fixture {
+        BrightnessMirrorViewModel(
+            brightnessMirrorShowingInteractor,
+            mainResources,
+            brightnessSliderControllerFactory,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt
new file mode 100644
index 0000000..43d6c48
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.unfold
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+var Kosmos.fullscreenLightRevealAnimationController by Fixture {
+    mock<FullscreenLightRevealAnimationController>()
+}
+var Kosmos.fullscreenLightRevealAnimationControllerFactory by Fixture {
+    var mockControllerFactory = mock<FullscreenLightRevealAnimationController.Factory>()
+    whenever(mockControllerFactory.create(any(), any(), any()))
+        .thenReturn(fullscreenLightRevealAnimationController)
+    mockControllerFactory
+}
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5e001fb..5075f63 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -30,9 +30,6 @@
         "FontNotoSerifSourceOverlay",
         "NavigationBarMode3ButtonOverlay",
         "NavigationBarModeGesturalOverlay",
-        "NavigationBarModeGesturalOverlayNarrowBack",
-        "NavigationBarModeGesturalOverlayWideBack",
-        "NavigationBarModeGesturalOverlayExtraWideBack",
         "TransparentNavigationBarOverlay",
         "NotesRoleEnabledOverlay",
         "preinstalled-packages-platform-overlays.xml",
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
deleted file mode 100644
index 28f9f33..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
-    name: "NavigationBarModeGesturalOverlayExtraWideBack",
-    theme: "NavigationBarModeGesturalExtraWideBack",
-    product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
deleted file mode 100644
index ba7beba..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.internal.systemui.navbar.gestural_extra_wide_back"
-        android:versionCode="1"
-        android:versionName="1.0">
-    <overlay android:targetPackage="android"
-        android:category="com.android.internal.navigation_bar_mode"
-        android:priority="1"/>
-
-    <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2023, The Android Open 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>
-    <!-- If true, attach the navigation bar to the app during app transition -->
-    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
deleted file mode 100644
index 120a489..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +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.
- */
--->
-<resources>
-    <!-- Controls the navigation bar interaction mode:
-         0: 3 button mode (back, home, overview buttons)
-         1: 2 button mode (back, home buttons + swipe up for overview)
-         2: gestures only for back, home and overview -->
-    <integer name="config_navBarInteractionMode">2</integer>
-
-    <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
-         Only applies if the device display is not square. -->
-    <bool name="config_navBarCanMove">false</bool>
-
-    <!-- Controls whether the navigation bar lets through taps. -->
-    <bool name="config_navBarTapThrough">true</bool>
-
-    <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
-    <bool name="config_imeDrawsImeNavBar">true</bool>
-
-    <!-- Controls the size of the back gesture inset. -->
-    <dimen name="config_backGestureInset">40dp</dimen>
-
-    <!-- Controls whether the navbar needs a scrim with
-     {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
-    <bool name="config_navBarNeedsScrim">false</bool>
-
-    <!-- Controls the opacity of the navigation bar depending on the visibility of the
-     various workspace stacks.
-     0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
-     1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
-         opaque.
-     2 - Nav bar is never forced opaque.
-     -->
-    <integer name="config_navBarOpacityMode">2</integer>
-
-    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
-         (which normally prevents seamless rotation). -->
-    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
-    <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
-         show. -->
-    <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
-    <!-- If true, attach the navigation bar to the app during app transition -->
-    <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc74..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +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.
- */
--->
-<resources>
-    <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
-    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
-    <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
-    <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_frame_height">48dp</dimen>
-    <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e047..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +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.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Name of overlay [CHAR LIMIT=64] -->
-    <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
deleted file mode 100644
index f8a5603..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
-    name: "NavigationBarModeGesturalOverlayNarrowBack",
-    theme: "NavigationBarModeGesturalNarrowBack",
-    product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
deleted file mode 100644
index 8de91c0..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.internal.systemui.navbar.gestural_narrow_back"
-        android:versionCode="1"
-        android:versionName="1.0">
-    <overlay android:targetPackage="android"
-        android:category="com.android.internal.navigation_bar_mode"
-        android:priority="1"/>
-
-    <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2023, The Android Open 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>
-    <!-- If true, attach the navigation bar to the app during app transition -->
-    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
deleted file mode 100644
index c18d892..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +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.
- */
--->
-<resources>
-    <!-- Controls the navigation bar interaction mode:
-         0: 3 button mode (back, home, overview buttons)
-         1: 2 button mode (back, home buttons + swipe up for overview)
-         2: gestures only for back, home and overview -->
-    <integer name="config_navBarInteractionMode">2</integer>
-
-    <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
-         Only applies if the device display is not square. -->
-    <bool name="config_navBarCanMove">false</bool>
-
-    <!-- Controls whether the navigation bar lets through taps. -->
-    <bool name="config_navBarTapThrough">true</bool>
-
-    <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
-    <bool name="config_imeDrawsImeNavBar">true</bool>
-
-    <!-- Controls the size of the back gesture inset. -->
-    <dimen name="config_backGestureInset">18dp</dimen>
-
-    <!-- Controls whether the navbar needs a scrim with
-     {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
-    <bool name="config_navBarNeedsScrim">false</bool>
-
-    <!-- Controls the opacity of the navigation bar depending on the visibility of the
-     various workspace stacks.
-     0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
-     1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
-         opaque.
-     2 - Nav bar is never forced opaque.
-     -->
-    <integer name="config_navBarOpacityMode">2</integer>
-
-    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
-         (which normally prevents seamless rotation). -->
-    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
-    <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
-         show. -->
-    <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
-    <!-- If true, attach the navigation bar to the app during app transition -->
-    <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
deleted file mode 100644
index 674bc74..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +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.
- */
--->
-<resources>
-    <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
-    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
-    <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
-    <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_frame_height">48dp</dimen>
-    <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
deleted file mode 100644
index bbab5e047..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +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.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Name of overlay [CHAR LIMIT=64] -->
-    <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
deleted file mode 100644
index 60ee6d5..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
-    name: "NavigationBarModeGesturalOverlayWideBack",
-    theme: "NavigationBarModeGesturalWideBack",
-    product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
deleted file mode 100644
index daf4613..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +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.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.internal.systemui.navbar.gestural_wide_back"
-        android:versionCode="1"
-        android:versionName="1.0">
-    <overlay android:targetPackage="android"
-        android:category="com.android.internal.navigation_bar_mode"
-        android:priority="1"/>
-
-    <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2023, The Android Open 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>
-    <!-- If true, attach the navigation bar to the app during app transition -->
-    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
deleted file mode 100644
index 877b5f8..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +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.
- */
--->
-<resources>
-    <!-- Controls the navigation bar interaction mode:
-         0: 3 button mode (back, home, overview buttons)
-         1: 2 button mode (back, home buttons + swipe up for overview)
-         2: gestures only for back, home and overview -->
-    <integer name="config_navBarInteractionMode">2</integer>
-
-    <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
-         Only applies if the device display is not square. -->
-    <bool name="config_navBarCanMove">false</bool>
-
-    <!-- Controls whether the navigation bar lets through taps. -->
-    <bool name="config_navBarTapThrough">true</bool>
-
-    <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
-    <bool name="config_imeDrawsImeNavBar">true</bool>
-
-    <!-- Controls the size of the back gesture inset. -->
-    <dimen name="config_backGestureInset">32dp</dimen>
-
-    <!-- Controls whether the navbar needs a scrim with
-     {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
-    <bool name="config_navBarNeedsScrim">false</bool>
-
-    <!-- Controls the opacity of the navigation bar depending on the visibility of the
-     various workspace stacks.
-     0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
-     1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
-         opaque.
-     2 - Nav bar is never forced opaque.
-     -->
-    <integer name="config_navBarOpacityMode">2</integer>
-
-    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
-         (which normally prevents seamless rotation). -->
-    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
-    <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
-         show. -->
-    <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
-    <!-- If true, attach the navigation bar to the app during app transition -->
-    <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc74..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +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.
- */
--->
-<resources>
-    <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
-    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
-    <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
-    <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_frame_height">48dp</dimen>
-    <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e047..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +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.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Name of overlay [CHAR LIMIT=64] -->
-    <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources>
\ No newline at end of file
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index a5b28ad..e77f846f 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,6 +5,17 @@
     },
     {
       "name": "RavenwoodBivalentTest_device"
+    },
+    {
+      "name": "SystemUIGoogleTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ],
   "ravenwood-presubmit": [
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 5e66b29..83f756e 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
@@ -42,7 +42,7 @@
     ALOGI("%s: JNI_OnLoad", __FILE__);
 
     int res = jniRegisterNativeMethods(env,
-            "com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest",
+            "com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest",
             sMethods, NELEM(sMethods));
     if (res < 0) {
         return res;
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
new file mode 100644
index 0000000..d91e734
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.ravenwoodtest.bivalenttest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.Size;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+// Tests for calling simple Android APIs.
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodAndroidApiTest {
+    @Test
+    public void testArrayMapSimple() {
+        final Map<String, String> map = new ArrayMap<>();
+
+        map.put("key1", "value1");
+        assertEquals("value1", map.get("key1"));
+    }
+
+    @Test
+    public void testSizeSimple() {
+        final var size = new Size(1, 2);
+
+        assertEquals(2, size.getHeight());
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
similarity index 68%
copy from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
copy to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
index 4b650b4..3a24c0e 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -13,35 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
 
-import android.platform.test.annotations.DisabledOnNonRavenwood;
 import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodClassRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Assert;
-import org.junit.Rule;
+import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public class RavenwoodRuleTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+@DisabledOnRavenwood
+public class RavenwoodClassRuleDeviceOnlyTest {
+    @ClassRule
+    public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
 
     @Test
-    @DisabledOnRavenwood
     public void testDeviceOnly() {
         Assert.assertFalse(RavenwoodRule.isOnRavenwood());
     }
-
-    @Test
-    @DisabledOnNonRavenwood
-    public void testRavenwoodOnly() {
-        Assert.assertTrue(RavenwoodRule.isOnRavenwood());
-    }
-
-    // TODO: Add more tests
 }
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
similarity index 61%
copy from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
copy to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
index 4b650b4..aa33dc3 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
@@ -13,35 +13,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
 
-import android.platform.test.annotations.DisabledOnNonRavenwood;
-import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodClassRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Assert;
-import org.junit.Rule;
+import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public class RavenwoodRuleTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+// TODO: atest RavenwoodBivalentTest_device fails with the following message.
+// `RUNNER ERROR: Instrumentation reported numtests=7 but only ran 6`
+// @android.platform.test.annotations.DisabledOnNonRavenwood
+// Figure it out and then make DisabledOnNonRavenwood support TYPEs as well.
+@Ignore
+public class RavenwoodClassRuleRavenwoodOnlyTest {
+    @ClassRule
+    public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
 
     @Test
-    @DisabledOnRavenwood
-    public void testDeviceOnly() {
-        Assert.assertFalse(RavenwoodRule.isOnRavenwood());
-    }
-
-    @Test
-    @DisabledOnNonRavenwood
     public void testRavenwoodOnly() {
         Assert.assertTrue(RavenwoodRule.isOnRavenwood());
     }
-
-    // TODO: Add more tests
 }
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
similarity index 95%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 3b106da..59467e9 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
 
 import static junit.framework.Assert.assertEquals;
 
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
similarity index 95%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
index 4b650b4..3edca7e 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
 
 import android.platform.test.annotations.DisabledOnNonRavenwood;
 import android.platform.test.annotations.DisabledOnRavenwood;
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
similarity index 92%
rename from ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
rename to ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
index 2cd585f..f1e33cb 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.platform.test.ravenwood.coretest;
+package com.android.ravenwoodtest.coretest;
 
 import android.platform.test.ravenwood.RavenwoodRule;
 
@@ -40,7 +40,7 @@
     public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
 
     public RavenwoodTestRunnerValidationTest() {
-        Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+        Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
         // Because RavenwoodRule will throw this error before executing the test method,
         // we can't do it in the test method itself.
         // So instead, we initialize it here.
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
index 8ca34ba..2fb8074 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -31,13 +31,17 @@
  * which means if a test class has this annotation, you can't negate it in subclasses or
  * on a per-method basis.
  *
+ * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT.
+ * See {@link com.android.ravenwoodtest.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest}
+ * for the reason.
+ *
  * The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
  * propagated to the device. (We may support it in the future, possibly using a debug. sysprop.)
  *
  * @hide
  */
 @Inherited
-@Target({ElementType.METHOD, ElementType.TYPE})
+@Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface DisabledOnNonRavenwood {
     /**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 9a4d488..f4b7ec36 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -25,6 +25,7 @@
 import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.EnabledOnRavenwood;
 
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -41,27 +42,16 @@
 public class RavenwoodClassRule implements TestRule {
     @Override
     public Statement apply(Statement base, Description description) {
-        // No special treatment when running outside Ravenwood; run tests as-is
         if (!IS_ON_RAVENWOOD) {
-            Assume.assumeTrue(shouldEnableOnDevice(description));
-            return base;
-        }
-
-        if (ENABLE_PROBE_IGNORED) {
+            // This should be "Assume", not Assert, but if we use assume here, the device side
+            // test runner would complain.
+            // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest.
+            Assert.assertTrue(shouldEnableOnDevice(description));
+        } else if (ENABLE_PROBE_IGNORED) {
             Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
-            // Pass through to possible underlying RavenwoodRule for both environment
-            // configuration and handling method-level annotations
-            return base;
         } else {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    Assume.assumeTrue(shouldEnableOnRavenwood(description));
-                    // Pass through to possible underlying RavenwoodRule for both environment
-                    // configuration and handling method-level annotations
-                    base.evaluate();
-                }
-            };
+            Assume.assumeTrue(shouldEnableOnRavenwood(description));
         }
+        return base;
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 52ea340..21d8019 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -28,7 +28,6 @@
 import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.EnabledOnRavenwood;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.util.ArraySet;
 
 import org.junit.Assume;
 import org.junit.rules.TestRule;
@@ -278,6 +277,12 @@
                 return false;
             }
         }
+        final var clazz = description.getTestClass();
+        if (clazz != null) {
+            if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) {
+                return false;
+            }
+        }
         return true;
     }
 
@@ -308,14 +313,17 @@
         }
 
         // Otherwise, consult any class-level annotations
-        if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
-            return true;
-        }
-        if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
-            return false;
-        }
-        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
-            return false;
+        final var clazz = description.getTestClass();
+        if (clazz != null) {
+            if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
+                return true;
+            }
+            if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
+                return false;
+            }
+            if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+                return false;
+            }
         }
 
         // When no annotations have been requested, assume test should be included
@@ -413,10 +421,9 @@
         };
     }
 
-    /**
-     * Do not use it outside ravenwood core classes.
-     */
-    public boolean _ravenwood_private$isOptionalValidationEnabled() {
-        return ENABLE_OPTIONAL_VALIDATION;
+    public static class _$RavenwoodPrivate {
+        public static boolean isOptionalValidationEnabled() {
+            return ENABLE_OPTIONAL_VALIDATION;
+        }
     }
 }
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
similarity index 97%
rename from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
rename to ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
index d02fe69..d566977 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
similarity index 96%
rename from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
rename to ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
index 0c137d5..aa2b7611 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
similarity index 97%
rename from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
rename to ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
index 9566710..fcc6c9c 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
similarity index 97%
rename from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
rename to ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
index efe468d..f833782 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
similarity index 98%
rename from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
rename to ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index c1dee5d..044239f 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/services/Android.bp b/services/Android.bp
index 881d6e1..6235195 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -241,6 +241,7 @@
     libs: [
         "android.hidl.manager-V1.0-java",
         "framework-tethering.stubs.module_lib",
+        "keepanno-annotations",
         "service-art.stubs.system_server",
         "service-permission.stubs.system_server",
         "service-rkp.stubs.system_server",
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 1d6399e..bfa1c7b 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -49,6 +49,13 @@
 }
 
 flag {
+    name: "enable_a11y_checker_logging"
+    namespace: "accessibility"
+    description: "Whether to identify and log app a11y issues."
+    bug: "325420273"
+}
+
+flag {
     name: "enable_magnification_joystick"
     namespace: "accessibility"
     description: "Whether to enable joystick controls for magnification"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0811c87..bbbc4ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2734,10 +2734,13 @@
                 userState.mComponentNameToServiceMap;
         boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
 
+        // Store the list of installed services.
+        mTempComponentNameSet.clear();
         for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
             AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
             ComponentName componentName = ComponentName.unflattenFromString(
                     installedService.getId());
+            mTempComponentNameSet.add(componentName);
 
             AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName);
 
@@ -2797,6 +2800,25 @@
             audioManager.setAccessibilityServiceUids(mTempIntArray);
         }
         mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray);
+
+        // If any services have been removed, remove them from the enabled list and the touch
+        // exploration granted list.
+        boolean anyServiceRemoved =
+                userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp))
+                        || userState.mTouchExplorationGrantedServices.removeIf(
+                                (comp) -> !mTempComponentNameSet.contains(comp));
+        if (anyServiceRemoved) {
+            // Update the enabled services setting.
+            persistComponentNamesToSettingLocked(
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    userState.mEnabledServices,
+                    userState.mUserId);
+            // Update the touch exploration granted services setting.
+            persistComponentNamesToSettingLocked(
+                    Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+                    userState.mTouchExplorationGrantedServices,
+                    userState.mUserId);
+        }
         updateAccessibilityEnabledSettingLocked(userState);
     }
 
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 0a3906a..ced10fb 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.autofill"
-container: "system"
 
 flag {
   name: "test"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1dc3b73..c130cee 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -1,5 +1,4 @@
 package: "android.service.autofill"
-container: "system"
 
 flag {
   name: "autofill_credman_integration"
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index d7e766e..f397814 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.server.companion.utils;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
 import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
@@ -209,7 +211,9 @@
      */
     public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
         if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
-                != PERMISSION_GRANTED) {
+                != PERMISSION_GRANTED
+                || context.checkCallingPermission(BLUETOOTH_SCAN) != PERMISSION_GRANTED
+                || context.checkCallingPermission(BLUETOOTH_CONNECT) != PERMISSION_GRANTED) {
             throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
                     + "permissions to request observing device presence base on the UUID");
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index f38d772b..23373f1 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -28,6 +28,7 @@
 import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
+import android.companion.virtual.flags.Flags;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
@@ -298,14 +299,28 @@
     public boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
             @Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
             int launchingFromDisplayId, boolean isNewTask) {
-        if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId, isNewTask)) {
-            notifyActivityBlocked(activityInfo);
-            return false;
-        }
-        if (mIntentListenerCallback != null && intent != null
-                && mIntentListenerCallback.shouldInterceptIntent(intent)) {
-            Slog.d(TAG, "Virtual device intercepting intent");
-            return false;
+        if (Flags.interceptIntentsBeforeApplyingPolicy()) {
+            if (mIntentListenerCallback != null && intent != null
+                    && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+                Slog.d(TAG, "Virtual device intercepting intent");
+                return false;
+            }
+            if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
+                    isNewTask)) {
+                notifyActivityBlocked(activityInfo);
+                return false;
+            }
+        } else {
+            if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
+                    isNewTask)) {
+                notifyActivityBlocked(activityInfo);
+                return false;
+            }
+            if (mIntentListenerCallback != null && intent != null
+                    && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+                Slog.d(TAG, "Virtual device intercepting intent");
+                return false;
+            }
         }
         return true;
     }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c7d9942..392c0c7 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -182,6 +182,7 @@
         "android.hardware.vibrator-V2-java",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
+        "keepanno-annotations",
         "service-art.stubs.system_server",
         "service-permission.stubs.system_server",
         "service-rkp.stubs.system_server",
@@ -213,7 +214,9 @@
         "android.hardware.health-V3-java", // AIDL
         "android.hardware.health-translate-java",
         "android.hardware.light-V1-java",
+        "android.hardware.security.authgraph-V1-java",
         "android.hardware.security.rkp-V3-java",
+        "android.hardware.security.secretkeeper-V1-java",
         "android.hardware.tv.cec-V1.1-java",
         "android.hardware.tv.hdmi.cec-V1-java",
         "android.hardware.tv.hdmi.connection-V1-java",
@@ -254,6 +257,7 @@
         "net_flags_lib",
         "stats_flags_lib",
         "core_os_flags_lib",
+        "connectivity_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index ac19d8b..4694e9f 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -70,6 +70,8 @@
     NotificationListener mNotificationListener;
     @Nullable
     private MediaProjectionManager mProjectionManager;
+
+    @GuardedBy("mSensitiveContentProtectionLock")
     @Nullable
     private MediaProjectionSession mMediaProjectionSession;
 
@@ -98,6 +100,48 @@
             mIsExempted = isExempted;
             mSessionId = sessionId;
         }
+
+        public void logProjectionSessionStart() {
+            FrameworkStatsLog.write(
+                    SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+                    mSessionId,
+                    mUid,
+                    mIsExempted,
+                    SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
+                    SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+            );
+        }
+
+        public void logProjectionSessionStop() {
+            FrameworkStatsLog.write(
+                    SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+                    mSessionId,
+                    mUid,
+                    mIsExempted,
+                    SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
+                    SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+            );
+        }
+
+        public void logAppBlocked(int uid) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+                    mSessionId,
+                    uid,
+                    mUid,
+                    FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
+            );
+        }
+
+        public void logAppUnblocked(int uid) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+                    mSessionId,
+                    uid,
+                    mUid,
+                    FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
+            );
+        }
     }
 
     private final MediaProjectionManager.Callback mProjectionCallback =
@@ -112,28 +156,11 @@
                     } finally {
                         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
                     }
-                    FrameworkStatsLog.write(
-                            SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
-                            mMediaProjectionSession.mSessionId,
-                            mMediaProjectionSession.mUid,
-                            mMediaProjectionSession.mIsExempted,
-                            SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
-                            SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
-                    );
                 }
 
                 @Override
                 public void onStop(MediaProjectionInfo info) {
                     if (DEBUG) Log.d(TAG, "onStop projection: " + info);
-                    FrameworkStatsLog.write(
-                            SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
-                            mMediaProjectionSession.mSessionId,
-                            mMediaProjectionSession.mUid,
-                            mMediaProjectionSession.mIsExempted,
-                            SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
-                            SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
-                    );
-
                     Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
                             "SensitiveContentProtectionManagerService.onProjectionStop");
                     try {
@@ -242,16 +269,18 @@
                 DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
         int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
                 projectionInfo.getUserHandle().getIdentifier());
-        mMediaProjectionSession = new MediaProjectionSession(
-                uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
-
-        if (isPackageExempted || isFeatureDisabled) {
-            Log.w(TAG, "projection session is exempted, package ="
-                    + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled);
-            return;
-        }
-
         synchronized (mSensitiveContentProtectionLock) {
+            mMediaProjectionSession = new MediaProjectionSession(
+                    uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
+            mMediaProjectionSession.logProjectionSessionStart();
+
+            if (isPackageExempted || isFeatureDisabled) {
+                Log.w(TAG, "projection session is exempted, package ="
+                        + projectionInfo.getPackageName() + ", isFeatureDisabled="
+                        + isFeatureDisabled);
+                return;
+            }
+
             mProjectionActive = true;
             if (sensitiveNotificationAppProtection()) {
                 updateAppsThatShouldBlockScreenCapture();
@@ -266,7 +295,10 @@
     private void onProjectionEnd() {
         synchronized (mSensitiveContentProtectionLock) {
             mProjectionActive = false;
-            mMediaProjectionSession = null;
+            if (mMediaProjectionSession != null) {
+                mMediaProjectionSession.logProjectionSessionStop();
+                mMediaProjectionSession = null;
+            }
 
             // notify windowmanager to clear any sensitive notifications observed during projection
             // session
@@ -437,22 +469,14 @@
             packageInfos.add(packageInfo);
             if (isShowingSensitiveContent) {
                 mWindowManager.addBlockScreenCaptureForApps(packageInfos);
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
-                        mMediaProjectionSession.mSessionId,
-                        uid,
-                        mMediaProjectionSession.mUid,
-                        FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
-                );
+                if (mMediaProjectionSession != null) {
+                    mMediaProjectionSession.logAppBlocked(uid);
+                }
             } else {
                 mWindowManager.removeBlockScreenCaptureForApps(packageInfos);
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
-                        mMediaProjectionSession.mSessionId,
-                        uid,
-                        mMediaProjectionSession.mUid,
-                        FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
-                );
+                if (mMediaProjectionSession != null) {
+                    mMediaProjectionSession.logAppUnblocked(uid);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index e7fae24..67e18ca 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -107,6 +107,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.DiskInfo;
+import android.os.storage.ICeStorageLockEventListener;
 import android.os.storage.IObbActionListener;
 import android.os.storage.IStorageEventListener;
 import android.os.storage.IStorageManager;
@@ -139,6 +140,7 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.AppFuseMount;
@@ -185,6 +187,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -602,6 +605,9 @@
     // Not guarded by lock, always used on the ActivityManager thread
     private final SparseArray<PackageMonitor> mPackageMonitorsForUser = new SparseArray<>();
 
+    /** List of listeners registered for ce storage callbacks */
+    private final CopyOnWriteArrayList<ICeStorageLockEventListener>
+            mCeStorageEventCallbacks = new CopyOnWriteArrayList<>();
 
     class ObbState implements IBinder.DeathRecipient {
         public ObbState(String rawPath, String canonicalPath, int callingUid,
@@ -3315,6 +3321,11 @@
         synchronized (mLock) {
             mCeUnlockedUsers.remove(userId);
         }
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            dispatchCeStorageLockedEvent(userId);
+        }
     }
 
     @Override
@@ -4580,6 +4591,18 @@
         return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
     }
 
+    @VisibleForTesting
+    CopyOnWriteArrayList<ICeStorageLockEventListener> getCeStorageEventCallbacks() {
+        return mCeStorageEventCallbacks;
+    }
+
+    @VisibleForTesting
+    void dispatchCeStorageLockedEvent(int userId) {
+        for (ICeStorageLockEventListener listener: mCeStorageEventCallbacks) {
+            listener.onStorageLocked(userId);
+        }
+    }
+
     private static class Callbacks extends Handler {
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
@@ -5066,5 +5089,23 @@
                 throw new IOException(e);
             }
         }
+
+        @Override
+        public void registerStorageLockEventListener(
+                @NonNull ICeStorageLockEventListener listener) {
+            boolean registered = mCeStorageEventCallbacks.add(listener);
+            if (!registered) {
+                Slog.w(TAG, "Failed to register listener: " + listener);
+            }
+        }
+
+        @Override
+        public void unregisterStorageLockEventListener(
+                @NonNull ICeStorageLockEventListener listener) {
+            boolean unregistered = mCeStorageEventCallbacks.remove(listener);
+            if (!unregistered) {
+                Slog.w(TAG, "Unregistering " + listener + " that was not registered");
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index baae40b..5933639 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -74,6 +74,15 @@
             "file_patterns": ["SensitiveContentProtectionManagerService\\.java"]
         },
         {
+            "name": "FrameworksMockingServicesTests",
+            "options": [
+                {
+                    "include-filter": "com.android.server.StorageManagerServiceTest"
+                }
+            ],
+            "file_patterns": ["StorageManagerService\\.java"]
+        },
+        {
             "name": "FrameworksServicesTests",
             "options": [
                 {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d49749b..ad15ea9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -635,7 +635,8 @@
     static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
     static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
     static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
-
+    static final String EXTRA_EXTRA_ATTACHMENT_URI =
+            "android.intent.extra.EXTRA_ATTACHMENT_URI";
     /**
      * It is now required for apps to explicitly set either
      * {@link android.content.Context#RECEIVER_EXPORTED} or
@@ -724,6 +725,9 @@
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
     final boolean mUseFifoUiScheduling;
 
+    /** Whether some specified important processes are allowed to use FIFO priority. */
+    boolean mAllowSpecifiedFifoScheduling = true;
+
     @GuardedBy("this")
     private final SparseArray<IUnsafeIntentStrictModeCallback>
             mStrictModeCallbacks = new SparseArray<>();
@@ -1047,6 +1051,10 @@
     @GuardedBy("this")
     final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
 
+    /** The processes that are allowed to use SCHED_FIFO prorioty. */
+    @GuardedBy("mProcLock")
+    final ArrayList<ProcessRecord> mSpecifiedFifoProcesses = new ArrayList<>();
+
     /**
      * List of records for processes that someone had tried to start before the
      * system was ready.  We don't start them at that point, but ensure they
@@ -7656,6 +7664,16 @@
      */
     public void requestBugReportWithDescription(@Nullable String shareTitle,
             @Nullable String shareDescription, int bugreportType, long nonce) {
+        requestBugReportWithDescription(shareTitle, shareDescription, bugreportType, nonce, null);
+    }
+
+    /**
+     * Takes a bugreport using bug report API ({@code BugreportManager}) which gets
+     * triggered by sending a broadcast to Shell. Optionally adds an extra attachment.
+     */
+    public void requestBugReportWithDescription(@Nullable String shareTitle,
+            @Nullable String shareDescription, int bugreportType, long nonce,
+            @Nullable Uri extraAttachment) {
         String type = null;
         switch (bugreportType) {
             case BugreportParams.BUGREPORT_MODE_FULL:
@@ -7710,6 +7728,10 @@
         triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
         triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
         triggerShellBugreport.putExtra(EXTRA_BUGREPORT_NONCE, nonce);
+        if (extraAttachment != null) {
+            triggerShellBugreport.putExtra(EXTRA_EXTRA_ATTACHMENT_URI, extraAttachment);
+            triggerShellBugreport.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        }
         triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         if (shareTitle != null) {
@@ -7763,6 +7785,15 @@
     }
 
     /**
+     * Takes an interactive bugreport with a progress notification. Also attaches given file uri.
+     */
+    @Override
+    public void requestBugReportWithExtraAttachment(@NonNull Uri extraAttachment) {
+        requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_INTERACTIVE, 0L,
+                extraAttachment);
+    }
+
+    /**
      * Takes an interactive bugreport with a progress notification. Also, shows the given title and
      * description on the final share notification
      */
@@ -8220,6 +8251,27 @@
         return false;
     }
 
+    /**
+     * Switches the priority between SCHED_FIFO and SCHED_OTHER for the main thread and render
+     * thread of the given process.
+     */
+    @GuardedBy("mProcLock")
+    static void setFifoPriority(@NonNull ProcessRecord app, boolean enable) {
+        final int pid = app.getPid();
+        final int renderThreadTid = app.getRenderThreadTid();
+        if (enable) {
+            scheduleAsFifoPriority(pid, true /* suppressLogs */);
+            if (renderThreadTid != 0) {
+                scheduleAsFifoPriority(renderThreadTid, true /* suppressLogs */);
+            }
+        } else {
+            scheduleAsRegularPriority(pid, true /* suppressLogs */);
+            if (renderThreadTid != 0) {
+                scheduleAsRegularPriority(renderThreadTid, true /* suppressLogs */);
+            }
+        }
+    }
+
     @Override
     public void setRenderThread(int tid) {
         synchronized (mProcLock) {
@@ -8245,7 +8297,7 @@
                 // promote to FIFO now
                 if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
                     if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
-                    if (mUseFifoUiScheduling) {
+                    if (proc.useFifoUiScheduling()) {
                         setThreadScheduler(proc.getRenderThreadTid(),
                                 SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
                     } else {
@@ -11282,6 +11334,9 @@
             if (mAlwaysFinishActivities) {
                 pw.println("  mAlwaysFinishActivities=" + mAlwaysFinishActivities);
             }
+            if (mAllowSpecifiedFifoScheduling) {
+                pw.println("  mAllowSpecifiedFifoScheduling=true");
+            }
             if (dumpAll) {
                 pw.println("  Total persistent processes: " + numPers);
                 pw.println("  mProcessesReady=" + mProcessesReady
@@ -17350,6 +17405,12 @@
                 }
             }
         }
+
+        if (com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()) {
+            synchronized (mProcLock) {
+                adjustFifoProcessesIfNeeded(uid, !active /* allowFifo */);
+            }
+        }
     }
 
     final boolean isCameraActiveForUid(@UserIdInt int uid) {
@@ -17358,6 +17419,34 @@
         }
     }
 
+    /**
+     * This is called when the given uid is using camera. If the uid has top process state, then
+     * cancel the FIFO priority of the high priority processes.
+     */
+    @VisibleForTesting
+    @GuardedBy("mProcLock")
+    void adjustFifoProcessesIfNeeded(int preemptiveUid, boolean allowSpecifiedFifo) {
+        if (allowSpecifiedFifo == mAllowSpecifiedFifoScheduling) {
+            return;
+        }
+        if (!allowSpecifiedFifo) {
+            final UidRecord uidRec = mProcessList.mActiveUids.get(preemptiveUid);
+            if (uidRec == null || uidRec.getCurProcState() > PROCESS_STATE_TOP) {
+                // To avoid frequent switching by background camera usages, e.g. face unlock,
+                // face detection (auto rotation), screen attention (keep screen on).
+                return;
+            }
+        }
+        mAllowSpecifiedFifoScheduling = allowSpecifiedFifo;
+        for (int i = mSpecifiedFifoProcesses.size() - 1; i >= 0; i--) {
+            final ProcessRecord proc = mSpecifiedFifoProcesses.get(i);
+            if (proc.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_TOP_APP) {
+                continue;
+            }
+            setFifoPriority(proc, allowSpecifiedFifo /* enable */);
+        }
+    }
+
     @GuardedBy("this")
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
@@ -17624,7 +17713,8 @@
 
     @Override
     public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
-            boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+            boolean runGc, String dumpBitmaps,
+            String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
         try {
             // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
             // its own permission (same as profileControl).
@@ -17658,7 +17748,8 @@
                         }
                     }, null);
 
-                thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
+                thread.dumpHeap(managed, mallocInfo, runGc, dumpBitmaps,
+                                path, fd, intermediateCallback);
                 fd = null;
                 return true;
             }
@@ -17870,9 +17961,35 @@
         mUserController.setStopUserOnSwitch(value);
     }
 
+    /** @deprecated use {@link #stopUserWithCallback(int, IStopUserCallback)} instead */
+    @Deprecated
     @Override
-    public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
-        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+    public int stopUser(final int userId,
+            boolean stopProfileRegardlessOfParent, final IStopUserCallback callback) {
+        return stopUserExceptCertainProfiles(userId, stopProfileRegardlessOfParent, callback);
+    }
+
+    /** Stops the given user. */
+    @Override
+    public int stopUserWithCallback(@UserIdInt int userId, @Nullable IStopUserCallback callback) {
+        return mUserController.stopUser(userId, /* allowDelayedLocking= */ false,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
+    }
+
+    /**
+     * Stops the given user.
+     *
+     * Usually, callers can just use @link{#stopUserWithCallback(int, IStopUserCallback)} instead.
+     *
+     * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+     *                                      parent is, e.g. even if the parent is the current user;
+     *                                      its value is irrelevant for non-profile users.
+     */
+    @Override
+    public int stopUserExceptCertainProfiles(@UserIdInt int userId,
+            boolean stopProfileRegardlessOfParent, @Nullable IStopUserCallback callback) {
+        return mUserController.stopUser(userId,
+                stopProfileRegardlessOfParent, /* allowDelayedLocking= */ false,
                 /* callback= */ callback, /* keyEvictedCallback= */ null);
     }
 
@@ -17881,11 +17998,9 @@
      * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
      *
      * <p>When delayed locking is not enabled through the overlay, this call becomes the same
-     * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+     * with {@link #stopUserWithCallback(int, IStopUserCallback)} call.
      *
      * @param userId User id to stop.
-     * @param force Force stop the user even if the user is related with system user or current
-     *              user.
      * @param callback Callback called when user has stopped.
      *
      * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
@@ -17895,9 +18010,8 @@
     // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
     // delayed locking behavior once the private space flag is finalized.
     @Override
-    public int stopUserWithDelayedLocking(final int userId, boolean force,
-            final IStopUserCallback callback) {
-        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+    public int stopUserWithDelayedLocking(@UserIdInt int userId, IStopUserCallback callback) {
+        return mUserController.stopUser(userId, /* allowDelayedLocking= */ true,
                 /* callback= */ callback, /* keyEvictedCallback= */ null);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5a97e87..e70722c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1239,6 +1239,7 @@
         final PrintWriter err = getErrPrintWriter();
         boolean managed = true;
         boolean mallocInfo = false;
+        String dumpBitmaps = null;
         int userId = UserHandle.USER_CURRENT;
         boolean runGc = false;
 
@@ -1257,6 +1258,11 @@
             } else if (opt.equals("-m")) {
                 managed = false;
                 mallocInfo = true;
+            } else if (opt.equals("-b")) {
+                dumpBitmaps = getNextArg();
+                if (dumpBitmaps == null) {
+                    dumpBitmaps = "png"; // default to PNG in dumping bitmaps
+                }
             } else {
                 err.println("Error: Unknown option: " + opt);
                 return -1;
@@ -1288,8 +1294,8 @@
             }
         }, null);
 
-        if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, heapFile, fd,
-                finishCallback)) {
+        if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, dumpBitmaps,
+                heapFile, fd, finishCallback)) {
             err.println("HEAP DUMP FAILED on process " + process);
             return -1;
         }
@@ -2554,7 +2560,8 @@
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 "shell_runStopUser-" + userId + "-[stopUser]");
         try {
-            int res = mInterface.stopUser(userId, force, callback);
+            int res = mInterface.stopUserExceptCertainProfiles(
+                    userId, /* stopProfileRegardlessOfParent= */ force, callback);
             if (res != ActivityManager.USER_OP_SUCCESS) {
                 String txt = "";
                 switch (res) {
@@ -4284,11 +4291,14 @@
             pw.println("      --user <USER_ID> | current: When supplying a process name,");
             pw.println("          specify user of process to profile; uses current user if not");
             pw.println("          specified.");
-            pw.println("  dumpheap [--user <USER_ID> current] [-n] [-g] <PROCESS> <FILE>");
+            pw.println("  dumpheap [--user <USER_ID> current] [-n] [-g] [-b <format>] ");
+            pw.println("           <PROCESS> <FILE>");
             pw.println("      Dump the heap of a process.  The given <PROCESS> argument may");
             pw.println("        be either a process name or pid.  Options are:");
             pw.println("      -n: dump native heap instead of managed heap");
             pw.println("      -g: force GC before dumping the heap");
+            pw.println("      -b <format>: dump contents of bitmaps in the format specified,");
+            pw.println("         which can be \"png\", \"jpg\" or \"webp\".");
             pw.println("      --user <USER_ID> | current: When supplying a process name,");
             pw.println("          specify user of process to dump; uses current user if not specified.");
             pw.println("  set-debug-app [-w] [--persistent] <PACKAGE>");
@@ -4385,7 +4395,7 @@
             pw.println("      Stop execution of USER_ID, not allowing it to run any");
             pw.println("      code until a later explicit start or switch to it.");
             pw.println("      -w: wait for stop-user to complete.");
-            pw.println("      -f: force stop even if there are related users that cannot be stopped.");
+            pw.println("      -f: force stop, even if user has an unstoppable parent.");
             pw.println("  is-user-stopped <USER_ID>");
             pw.println("      Returns whether <USER_ID> has been stopped or not.");
             pw.println("  get-started-user-state <USER_ID>");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 51aae77..6c16fba0 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1051,7 +1051,9 @@
                                     + mProfile.mApp + " to " + mDumpUri.getPath());
                         }
                         thread.dumpHeap(/* managed= */ true,
-                                /* mallocInfo= */ false, /* runGc= */ false,
+                                /* mallocInfo= */ false,
+                                /* runGc= */ false,
+                                /* dumpbitmaps= */ null,
                                 mDumpUri.getPath(), fd,
                                 /* finishCallback= */ null);
                     } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5a750c2..ea7a21d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,7 +72,6 @@
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
 import static android.media.audio.Flags.roForegroundAudioControl;
-import static android.os.Process.SCHED_OTHER;
 import static android.os.Process.THREAD_GROUP_BACKGROUND;
 import static android.os.Process.THREAD_GROUP_DEFAULT;
 import static android.os.Process.THREAD_GROUP_RESTRICTED;
@@ -81,7 +80,6 @@
 import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
 import static android.os.Process.setProcessGroup;
 import static android.os.Process.setThreadPriority;
-import static android.os.Process.setThreadScheduler;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
@@ -3315,22 +3313,10 @@
                         // do nothing if we already switched to RT
                         if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
                             app.getWindowProcessController().onTopProcChanged();
-                            if (mService.mUseFifoUiScheduling) {
+                            if (app.useFifoUiScheduling()) {
                                 // Switch UI pipeline for app to SCHED_FIFO
                                 state.setSavedPriority(Process.getThreadPriority(app.getPid()));
-                                mService.scheduleAsFifoPriority(app.getPid(), true);
-                                if (renderThreadTid != 0) {
-                                    mService.scheduleAsFifoPriority(renderThreadTid,
-                                            /* suppressLogs */true);
-                                    if (DEBUG_OOM_ADJ) {
-                                        Slog.d("UI_FIFO", "Set RenderThread (TID " +
-                                                renderThreadTid + ") to FIFO");
-                                    }
-                                } else {
-                                    if (DEBUG_OOM_ADJ) {
-                                        Slog.d("UI_FIFO", "Not setting RenderThread TID");
-                                    }
-                                }
+                                ActivityManagerService.setFifoPriority(app, true /* enable */);
                             } else {
                                 // Boost priority for top app UI and render threads
                                 setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
@@ -3347,22 +3333,10 @@
                     } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
                             && curSchedGroup != SCHED_GROUP_TOP_APP) {
                         app.getWindowProcessController().onTopProcChanged();
-                        if (mService.mUseFifoUiScheduling) {
-                            try {
-                                // Reset UI pipeline to SCHED_OTHER
-                                setThreadScheduler(app.getPid(), SCHED_OTHER, 0);
-                                setThreadPriority(app.getPid(), state.getSavedPriority());
-                                if (renderThreadTid != 0) {
-                                    setThreadScheduler(renderThreadTid,
-                                            SCHED_OTHER, 0);
-                                }
-                            } catch (IllegalArgumentException e) {
-                                Slog.w(TAG,
-                                        "Failed to set scheduling policy, thread does not exist:\n"
-                                                + e);
-                            } catch (SecurityException e) {
-                                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
-                            }
+                        if (app.useFifoUiScheduling()) {
+                            // Reset UI pipeline to SCHED_OTHER
+                            ActivityManagerService.setFifoPriority(app, false /* enable */);
+                            setThreadPriority(app.getPid(), state.getSavedPriority());
                         } else {
                             // Reset priority for top app UI and render threads
                             setThreadPriority(app.getPid(), 0);
@@ -3557,7 +3531,7 @@
                 // {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
                 // is not ready when attaching.
                 app.getWindowProcessController().onTopProcChanged();
-                if (mService.mUseFifoUiScheduling) {
+                if (app.useFifoUiScheduling()) {
                     mService.scheduleAsFifoPriority(app.getPid(), true);
                 } else {
                     setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b939089..0816527 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -745,6 +745,9 @@
             mOnewayThread = thread;
         }
         mWindowProcessController.setThread(thread);
+        if (mWindowProcessController.useFifoUiScheduling()) {
+            mService.mSpecifiedFifoProcesses.add(this);
+        }
     }
 
     @GuardedBy({"mService", "mProcLock"})
@@ -752,9 +755,19 @@
         mThread = null;
         mOnewayThread = null;
         mWindowProcessController.setThread(null);
+        if (mWindowProcessController.useFifoUiScheduling()) {
+            mService.mSpecifiedFifoProcesses.remove(this);
+        }
         mProfile.onProcessInactive(tracker);
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean useFifoUiScheduling() {
+        return mService.mUseFifoUiScheduling
+                || (mService.mAllowSpecifiedFifoScheduling
+                        && mWindowProcessController.useFifoUiScheduling());
+    }
+
     @GuardedBy("mService")
     int getDyingPid() {
         return mDyingPid;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 60a8b50..dd4cee4 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -246,6 +246,7 @@
      *
      * <p>Note: Current and system user (and their related profiles) are never stopped when
      * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+     // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
      */
     @GuardedBy("mLock")
     private int mMaxRunningUsers;
@@ -578,7 +579,8 @@
             // from outside.
             Slogf.i(TAG, "Too many running users (%d). Attempting to stop user %d",
                     currentlyRunningLru.size(), userId);
-            if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+            if (stopUsersLU(userId,
+                    /* stopProfileRegardlessOfParent= */ false, /* allowDelayedLocking= */ true,
                     /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
                     == USER_OP_SUCCESS) {
                 // Technically, stopUsersLU can remove more than one user when stopping a parent.
@@ -875,7 +877,7 @@
             Slogf.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
             // Pre-created user was started right after creation so services could properly
             // intialize it; it should be stopped right away as it's not really a "real" user.
-            stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+            stopUser(userInfo.id, /* allowDelayedLocking= */ false,
                     /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
             return;
         }
@@ -930,7 +932,7 @@
     }
 
     int restartUser(final int userId, @UserStartMode int userStartMode) {
-        return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+        return stopUser(userId, /* allowDelayedLocking= */ false,
                 /* stopUserCallback= */ null, new KeyEvictedCallback() {
                     @Override
                     public void keyEvicted(@UserIdInt int userId) {
@@ -966,18 +968,24 @@
 
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
-            return stopUsersLU(userId, /* force= */ true, /* allowDelayedLocking= */
-                    false, /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+            return stopUsersLU(userId, /* allowDelayedLocking= */ false,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
                     == ActivityManager.USER_OP_SUCCESS;
         }
     }
 
-    int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+    int stopUser(final int userId, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+        return stopUser(userId, true, allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+    }
+
+    int stopUser(final int userId,
+            final boolean stopProfileRegardlessOfParent, final boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
 
         t.traceBegin("UserController"
-                + (force ? "-force" : "")
+                + (stopProfileRegardlessOfParent ? "-stopProfileRegardlessOfParent" : "")
                 + (allowDelayedLocking ? "-allowDelayedLocking" : "")
                 + (stopUserCallback != null ? "-withStopUserCallback" : "")
                 + "-" + userId + "-[stopUser]");
@@ -987,20 +995,32 @@
 
             enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
             synchronized (mLock) {
-                return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
-                        keyEvictedCallback);
+                return stopUsersLU(userId, stopProfileRegardlessOfParent, allowDelayedLocking,
+                        stopUserCallback, keyEvictedCallback);
             }
         } finally {
             t.traceEnd();
         }
     }
 
+    /** Stops the user along with its profiles. */
+    @GuardedBy("mLock")
+    private int stopUsersLU(final int userId, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+        return stopUsersLU(userId, /* stopProfileRegardlessOfParent= */ true,
+                allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+    }
+
     /**
      * Stops the user along with its profiles. The method calls
      * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
+     *
+     * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+     *                                      parent is, e.g. even if the parent is the current user
      */
     @GuardedBy("mLock")
-    private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
+    private int stopUsersLU(final int userId,
+            boolean stopProfileRegardlessOfParent, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
@@ -1008,34 +1028,28 @@
         if (isCurrentUserLU(userId)) {
             return USER_OP_IS_CURRENT;
         }
-        // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime...
-        final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
-        if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
-            if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) {
-                return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+        if (!stopProfileRegardlessOfParent) {
+            final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+            if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
+                // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
+                if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId))) {
+                    return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+                }
             }
         }
-        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        int[] usersToStop = getUsersToStopLU(userId);
-        // If one of related users is system or current, no related users should be stopped
+        final int[] usersToStop = getUsersToStopLU(userId);
+
+        // Final safety check: abort if one of the users we would plan to stop must not be stopped.
+        // This should be impossible in the current code, but just in case.
         for (int relatedUserId : usersToStop) {
             if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
-                if (DEBUG_MU) {
-                    Slogf.i(TAG, "stopUsersLocked cannot stop related user " + relatedUserId);
-                }
-                // We still need to stop the requested user if it's a force stop.
-                if (force) {
-                    Slogf.i(TAG,
-                            "Force stop user " + userId + ". Related users will not be stopped");
-                    t.traceBegin("stopSingleUserLU-force-" + userId + "-[stopUser]");
-                    stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
-                            keyEvictedCallback);
-                    t.traceEnd();
-                    return USER_OP_SUCCESS;
-                }
+                Slogf.e(TAG, "Cannot stop user %d because it is related to user %d. ",
+                        userId, relatedUserId);
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
             }
         }
+
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
             t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]");
@@ -1304,8 +1318,8 @@
             mInjector.activityManagerOnUserStopped(userId);
             // Clean up all state and processes associated with the user.
             // Kill all the processes for the user.
-            t.traceBegin("forceStopUser-" + userId + "-[stopUser]");
-            forceStopUser(userId, "finish user");
+            t.traceBegin("stopPackagesOfStoppedUser-" + userId + "-[stopUser]");
+            stopPackagesOfStoppedUser(userId, "finish user");
             t.traceEnd();
         }
 
@@ -1516,8 +1530,8 @@
         return userIds.toArray();
     }
 
-    private void forceStopUser(@UserIdInt int userId, String reason) {
-        if (DEBUG_MU) Slogf.i(TAG, "forceStopUser(%d): %s", userId, reason);
+    private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
+        if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
         mInjector.activityManagerForceStopPackage(userId, reason);
         if (mInjector.getUserManager().isPreCreated(userId)) {
             // Don't fire intent for precreated.
@@ -1566,8 +1580,7 @@
             // This is a user to be stopped.
             Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
             synchronized (mLock) {
-                stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
-                        null, null);
+                stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
             }
         }
     }
@@ -2259,8 +2272,7 @@
             // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
             if (hasRestriction || shouldStopUserOnSwitch()) {
                 Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
-                stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ false,
-                        null, null);
+                stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
                 return;
             }
         }
@@ -2275,7 +2287,8 @@
                 Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
                 synchronized (mLock) {
                     stopUsersLU(profileUserId,
-                            /* force= */ true, /* allowDelayedLocking= */ false, null, null);
+                            /* stopProfileRegardlessOfParent= */ false,
+                            /* allowDelayedLocking= */ false, null, null);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig
index 54e4571..0673013 100644
--- a/services/core/java/com/android/server/app/flags.aconfig
+++ b/services/core/java/com/android/server/app/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.server.app"
-container: "system"
 
 flag {
     name: "game_default_frame_rate"
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5ba0af4..9896d9d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5464,19 +5464,19 @@
 
     /** @see AudioManager#setMicrophoneMuteFromSwitch(boolean) */
     public void setMicrophoneMuteFromSwitch(boolean on) {
-        int userId = Binder.getCallingUid();
-        if (userId != android.os.Process.SYSTEM_UID) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != android.os.Process.SYSTEM_UID) {
             Log.e(TAG, "setMicrophoneMuteFromSwitch() called from non system user!");
             return;
         }
         mMicMuteFromSwitch = on;
         new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
-                .setUid(userId)
+                .setUid(callingUid)
                 .set(MediaMetrics.Property.EVENT, "setMicrophoneMuteFromSwitch")
                 .set(MediaMetrics.Property.REQUEST, on
                         ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE)
                 .record();
-        setMicrophoneMuteNoCallerCheck(userId);
+        setMicrophoneMuteNoCallerCheck(UserHandle.getCallingUserId());
     }
 
     private void setMicMuteFromSwitchInput() {
@@ -5507,9 +5507,10 @@
         if (DEBUG_VOL) {
             Log.d(TAG, String.format("Mic mute %b, user=%d", muted, userId));
         }
-        // only mute for the current user
-        if (getCurrentUserId() == userId || userId == android.os.Process.SYSTEM_UID) {
+        // only mute for the current user or for the system user.
+        if (getCurrentUserId() == userId || userId == UserHandle.USER_SYSTEM) {
             final boolean currentMute = mAudioSystem.isMicrophoneMuted();
+            int callingUid = Binder.getCallingUid();
             final long identity = Binder.clearCallingIdentity();
             try {
                 final int ret = mAudioSystem.muteMicrophone(muted);
@@ -5522,7 +5523,7 @@
                 }
 
                 new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
-                        .setUid(userId)
+                        .setUid(callingUid)
                         .set(MediaMetrics.Property.EVENT, "setMicrophoneMuteNoCallerCheck")
                         .set(MediaMetrics.Property.MUTE, mMicMuteFromSystemCached
                                 ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 5b9469b..1ea72d7 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -34,10 +34,11 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-import com.google.android.collect.Sets;
 
 import com.android.server.backup.Flags;
 
+import com.google.android.collect.Sets;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Set;
@@ -64,6 +65,7 @@
     private static final String APP_LOCALES_HELPER = "app_locales";
     private static final String APP_GENDER_HELPER = "app_gender";
     private static final String COMPANION_HELPER = "companion";
+    private static final String SYSTEM_GENDER_HELPER = "system_gender";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -99,7 +101,9 @@
                     NOTIFICATION_HELPER,
                     SYNC_SETTINGS_HELPER,
                     APP_LOCALES_HELPER,
-                    COMPANION_HELPER);
+                    COMPANION_HELPER,
+                    APP_GENDER_HELPER,
+                    SYSTEM_GENDER_HELPER);
 
     /** Helpers that are enabled for full, non-system users. */
     private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -139,6 +143,8 @@
         addHelperIfEligibleForUser(APP_GENDER_HELPER,
                 new AppGrammaticalGenderBackupHelper(mUserId));
         addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
+        addHelperIfEligibleForUser(SYSTEM_GENDER_HELPER,
+                new SystemGrammaticalGenderBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java b/services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java
new file mode 100644
index 0000000..c0d398e
--- /dev/null
+++ b/services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.backup;
+
+import static android.app.backup.BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+
+import android.annotation.UserIdInt;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BlobBackupHelper;
+import android.os.ParcelFileDescriptor;
+
+import com.android.server.LocalServices;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
+
+public class SystemGrammaticalGenderBackupHelper extends BlobBackupHelper {
+    private static final int BLOB_VERSION = 1;
+    private static final String KEY_SYSTEM_GENDER = "system_gender";
+
+    private final @UserIdInt int mUserId;
+    private final GrammaticalInflectionManagerInternal mGrammarInflectionManagerInternal;
+
+    public SystemGrammaticalGenderBackupHelper(int userId) {
+        super(BLOB_VERSION, KEY_SYSTEM_GENDER);
+        mUserId = userId;
+        mGrammarInflectionManagerInternal = LocalServices.getService(
+                GrammaticalInflectionManagerInternal.class);
+    }
+
+    @Override
+    public void performBackup(ParcelFileDescriptor oldStateFd, BackupDataOutput data,
+            ParcelFileDescriptor newStateFd) {
+        // Only backup the gender data if e2e encryption is present
+        if ((data.getTransportFlags() & FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED) == 0) {
+            return;
+        }
+
+        super.performBackup(oldStateFd, data, newStateFd);
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        return KEY_SYSTEM_GENDER.equals(key) && mGrammarInflectionManagerInternal != null
+                ? mGrammarInflectionManagerInternal.getSystemBackupPayload(mUserId) : null;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        if (KEY_SYSTEM_GENDER.equals(key) && mGrammarInflectionManagerInternal != null) {
+            mGrammarInflectionManagerInternal.applyRestoredSystemPayload(payload, mUserId);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 3d4801b..c03b3b8 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -56,7 +56,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -111,7 +111,7 @@
     @NonNull private final BiometricContext mBiometricContext;
     private final IStatusBarService mStatusBarService;
     @VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver;
-    private final KeyStore mKeyStore;
+    private final KeyStoreAuthorization mKeyStoreAuthorization;
     private final Random mRandom;
     private final ClientDeathReceiver mClientDeathReceiver;
     final PreAuthInfo mPreAuthInfo;
@@ -158,7 +158,7 @@
             @NonNull BiometricContext biometricContext,
             @NonNull IStatusBarService statusBarService,
             @NonNull IBiometricSysuiReceiver sysuiReceiver,
-            @NonNull KeyStore keystore,
+            @NonNull KeyStoreAuthorization keyStoreAuthorization,
             @NonNull Random random,
             @NonNull ClientDeathReceiver clientDeathReceiver,
             @NonNull PreAuthInfo preAuthInfo,
@@ -172,8 +172,8 @@
             @NonNull PromptInfo promptInfo,
             boolean debugEnabled,
             @NonNull List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties) {
-        this(context, biometricContext, statusBarService, sysuiReceiver, keystore, random,
-                clientDeathReceiver, preAuthInfo, token, requestId, operationId, userId,
+        this(context, biometricContext, statusBarService, sysuiReceiver, keyStoreAuthorization,
+                random, clientDeathReceiver, preAuthInfo, token, requestId, operationId, userId,
                 sensorReceiver, clientReceiver, opPackageName, promptInfo, debugEnabled,
                 fingerprintSensorProperties, BiometricFrameworkStatsLogger.getInstance());
     }
@@ -183,7 +183,7 @@
             @NonNull BiometricContext biometricContext,
             @NonNull IStatusBarService statusBarService,
             @NonNull IBiometricSysuiReceiver sysuiReceiver,
-            @NonNull KeyStore keystore,
+            @NonNull KeyStoreAuthorization keyStoreAuthorization,
             @NonNull Random random,
             @NonNull ClientDeathReceiver clientDeathReceiver,
             @NonNull PreAuthInfo preAuthInfo,
@@ -203,7 +203,7 @@
         mBiometricContext = biometricContext;
         mStatusBarService = statusBarService;
         mSysuiReceiver = sysuiReceiver;
-        mKeyStore = keystore;
+        mKeyStoreAuthorization = keyStoreAuthorization;
         mRandom = random;
         mClientDeathReceiver = clientDeathReceiver;
         mPreAuthInfo = preAuthInfo;
@@ -814,14 +814,14 @@
             switch (reason) {
                 case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
                     if (credentialAttestation != null) {
-                        mKeyStore.addAuthToken(credentialAttestation);
+                        mKeyStoreAuthorization.addAuthToken(credentialAttestation);
                     } else {
                         Slog.e(TAG, "credentialAttestation is null");
                     }
                 case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
                 case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
                     if (mTokenEscrow != null) {
-                        final int result = mKeyStore.addAuthToken(mTokenEscrow);
+                        final int result = mKeyStoreAuthorization.addAuthToken(mTokenEscrow);
                         Slog.d(TAG, "addAuthToken: " + result);
                     } else {
                         Slog.e(TAG, "mTokenEscrow is null");
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 894b4d5..3737d6f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -65,15 +65,11 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.security.Authorization;
 import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.authorization.IKeystoreAuthorization;
-import android.security.authorization.ResponseCode;
+import android.security.KeyStoreAuthorization;
 import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -123,11 +119,9 @@
     @VisibleForTesting
     IStatusBarService mStatusBarService;
     @VisibleForTesting
-    KeyStore mKeyStore;
-    @VisibleForTesting
     ITrustManager mTrustManager;
     @VisibleForTesting
-    IKeystoreAuthorization mKeystoreAuthorization;
+    KeyStoreAuthorization mKeyStoreAuthorization;
     @VisibleForTesting
     IGateKeeperService mGateKeeper;
 
@@ -674,19 +668,7 @@
             int[] authTypesArray = hardwareAuthenticators.stream()
                     .mapToInt(Integer::intValue)
                     .toArray();
-            try {
-                return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Error getting last auth time: " + e);
-                return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
-            } catch (ServiceSpecificException e) {
-                // This is returned when the feature flag test fails in keystore2
-                if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
-                    throw new UnsupportedOperationException();
-                }
-
-                return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
-            }
+            return mKeyStoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@@ -1011,8 +993,8 @@
             return ActivityManager.getService();
         }
 
-        public IKeystoreAuthorization getKeystoreAuthorizationService() {
-            return Authorization.getService();
+        public KeyStoreAuthorization getKeyStoreAuthorization() {
+            return KeyStoreAuthorization.getInstance();
         }
 
         public IGateKeeperService getGateKeeperService() {
@@ -1036,10 +1018,6 @@
             return new SettingObserver(context, handler, callbacks);
         }
 
-        public KeyStore getKeyStore() {
-            return KeyStore.getInstance();
-        }
-
         /**
          * Allows to enable/disable debug logs.
          */
@@ -1138,7 +1116,7 @@
         mBiometricContext = injector.getBiometricContext(context);
         mUserManager = injector.getUserManager(context);
         mBiometricCameraManager = injector.getBiometricCameraManager(context);
-        mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
+        mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
         mGateKeeper = injector.getGateKeeperService();
         mBiometricNotificationLogger = injector.getNotificationLogger();
 
@@ -1159,7 +1137,6 @@
 
     @Override
     public void onStart() {
-        mKeyStore = mInjector.getKeyStore();
         mStatusBarService = mInjector.getStatusBarService();
         mTrustManager = mInjector.getTrustManager();
         mInjector.publishBinderService(this, mImpl);
@@ -1481,7 +1458,7 @@
 
         final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
         mAuthSession = new AuthSession(getContext(), mBiometricContext, mStatusBarService,
-                createSysuiReceiver(requestId), mKeyStore, mRandom,
+                createSysuiReceiver(requestId), mKeyStoreAuthorization, mRandom,
                 createClientDeathReceiver(requestId), preAuthInfo, token, requestId,
                 operationId, userId, createBiometricSensorReceiver(requestId), receiver,
                 opPackageName, promptInfo, debugEnabled,
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index f51b62d..4af30a9 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -89,11 +89,23 @@
         return true;
     }
 
-    /** If virtualized biometrics are supported (requires debug build). */
-    public static boolean isVirtualEnabled(@NonNull Context context) {
+    /** If virtualized fingerprint sensor is supported. */
+    public static boolean isFingerprintVirtualEnabled(@NonNull Context context) {
         return Build.isDebuggable()
-                && Settings.Secure.getIntForUser(context.getContentResolver(),
-                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+                && (Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 0,
+                UserHandle.USER_CURRENT) == 1
+                || Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1);
+    }
+
+    /** If virtualized face sensor is supported. */
+    public static boolean isFaceVirtualEnabled(@NonNull Context context) {
+        return Build.isDebuggable()
+                && (Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1
+                || Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1);
     }
 
     /**
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 62c21cf..4fa8741 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -30,7 +30,7 @@
 import android.hardware.biometrics.BiometricRequestConstants;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
 import android.util.EventLog;
 import android.util.Slog;
 
@@ -255,7 +255,7 @@
 
             // For BP, BiometricService will add the authToken to Keystore.
             if (!isBiometricPrompt() && mIsStrongBiometric) {
-                final int result = KeyStore.getInstance().addAuthToken(byteToken);
+                final int result = KeyStoreAuthorization.getInstance().addAuthToken(byteToken);
                 if (result != 0) {
                     Slog.d(TAG, "Error adding auth token : " + result);
                 } else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 1037124..a946af8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -748,7 +748,7 @@
             final String virtualInstance = "virtual";
             final boolean isVirtualHalPresent =
                     faceSensorConfigurations.doesInstanceExist(virtualInstance);
-            if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+            if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
                 if (isVirtualHalPresent) {
                     return new Pair<>(virtualInstance,
                             faceSensorConfigurations.getSensorPropForInstance(virtualInstance));
@@ -786,7 +786,7 @@
             }
 
             final int virtualAt = aidlInstances.indexOf("virtual");
-            if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+            if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
                 if (virtualAt != -1) {
                     //only virtual instance should be returned
                     Slog.i(TAG, "virtual hal is used");
@@ -928,7 +928,7 @@
 
     void syncEnrollmentsNow() {
         Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
-        if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+        if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
             for (ServiceProvider provider : mRegistry.getProviders()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2dc03ed..d762914 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1136,7 +1136,7 @@
         final String virtualInstance = "virtual";
         final boolean isVirtualHalPresent =
                 fingerprintSensorConfigurations.doesInstanceExist(virtualInstance);
-        if (Utils.isVirtualEnabled(getContext())) {
+        if (Utils.isFingerprintVirtualEnabled(getContext())) {
             if (isVirtualHalPresent) {
                 return new Pair<>(virtualInstance,
                         fingerprintSensorConfigurations.getSensorPropForInstance(virtualInstance));
@@ -1169,7 +1169,7 @@
         }
 
         final int virtualAt = aidlInstances.indexOf("virtual");
-        if (Utils.isVirtualEnabled(getContext())) {
+        if (Utils.isFingerprintVirtualEnabled(getContext())) {
             if (virtualAt != -1) {
                 //only virtual instance should be returned
                 Slog.i(TAG, "virtual hal is used");
@@ -1295,7 +1295,7 @@
 
     void syncEnrollmentsNow() {
         Utils.checkPermissionOrShell(getContext(), MANAGE_FINGERPRINT);
-        if (Utils.isVirtualEnabled(getContext())) {
+        if (Utils.isFingerprintVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
             final CountDownLatch latch = new CountDownLatch(mRegistry.getProviders().size());
@@ -1324,7 +1324,7 @@
     }
 
     void simulateVhalFingerDown() {
-        if (Utils.isVirtualEnabled(getContext())) {
+        if (Utils.isFingerprintVirtualEnabled(getContext())) {
             Slog.i(TAG, "Simulate virtual HAL finger down event");
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider != null) {
diff --git a/services/core/java/com/android/server/connectivity/Android.bp b/services/core/java/com/android/server/connectivity/Android.bp
new file mode 100644
index 0000000..a374ec2
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+    name: "connectivity_flags",
+    package: "com.android.server.connectivity",
+    srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "connectivity_flags_lib",
+    aconfig_declarations: "connectivity_flags",
+}
diff --git a/services/core/java/com/android/server/connectivity/flags.aconfig b/services/core/java/com/android/server/connectivity/flags.aconfig
new file mode 100644
index 0000000..32593d4
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.connectivity"
+
+flag {
+    name: "replace_vpn_profile_store"
+    namespace: "android_core_networking"
+    description: "This flag controls the usage of VpnBlobStore to replace LegacyVpnProfileStore."
+    bug: "307903113"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 06dc7f5..9c020a7 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -425,7 +425,7 @@
         return mScreenAutoBrightness;
     }
 
-    float getRawAutomaticScreenBrightness() {
+    public float getRawAutomaticScreenBrightness() {
         return mRawScreenAutoBrightness;
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index d50a43a..e3e3be2 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -18,6 +18,7 @@
 
 import android.text.TextUtils;
 
+import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.brightness.BrightnessReason;
 
 import java.util.Objects;
@@ -43,6 +44,8 @@
 
     private final float mCustomAnimationRate;
 
+    private final BrightnessEvent mBrightnessEvent;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
@@ -54,6 +57,7 @@
         mMinBrightness = builder.getMinBrightness();
         mCustomAnimationRate = builder.getCustomAnimationRate();
         mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
+        mBrightnessEvent = builder.getBrightnessEvent();
     }
 
     /**
@@ -127,6 +131,13 @@
         return mShouldUpdateScreenBrightnessSetting;
     }
 
+    /**
+     * @return The BrightnessEvent object
+     */
+    public BrightnessEvent getBrightnessEvent() {
+        return mBrightnessEvent;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -144,6 +155,8 @@
         stringBuilder.append("\n    customAnimationRate:").append(mCustomAnimationRate);
         stringBuilder.append("\n    shouldUpdateScreenBrightnessSetting:")
                 .append(mShouldUpdateScreenBrightnessSetting);
+        stringBuilder.append("\n    mBrightnessEvent:")
+                .append(Objects.toString(mBrightnessEvent, "null"));
         return stringBuilder.toString();
     }
 
@@ -173,7 +186,8 @@
                 && mMinBrightness == otherState.getMinBrightness()
                 && mCustomAnimationRate == otherState.getCustomAnimationRate()
                 && mShouldUpdateScreenBrightnessSetting
-                    == otherState.shouldUpdateScreenBrightnessSetting();
+                    == otherState.shouldUpdateScreenBrightnessSetting()
+                && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent());
     }
 
     @Override
@@ -181,7 +195,7 @@
         return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
                 mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
                 mCustomAnimationRate,
-                mShouldUpdateScreenBrightnessSetting);
+                mShouldUpdateScreenBrightnessSetting, mBrightnessEvent);
     }
 
     /**
@@ -206,6 +220,8 @@
         private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
         private boolean mShouldUpdateScreenBrightnessSetting;
 
+        private BrightnessEvent mBrightnessEvent;
+
         /**
          * Create a builder starting with the values from the specified {@link
          * DisplayBrightnessState}.
@@ -225,6 +241,7 @@
             builder.setCustomAnimationRate(state.getCustomAnimationRate());
             builder.setShouldUpdateScreenBrightnessSetting(
                     state.shouldUpdateScreenBrightnessSetting());
+            builder.setBrightnessEvent(state.getBrightnessEvent());
             return builder;
         }
 
@@ -400,5 +417,22 @@
         public DisplayBrightnessState build() {
             return new DisplayBrightnessState(this);
         }
+
+
+        /**
+         * This is used to get the BrightnessEvent object from its builder
+         */
+        public BrightnessEvent getBrightnessEvent() {
+            return mBrightnessEvent;
+        }
+
+
+        /**
+         * This is used to set the BrightnessEvent object
+         */
+        public Builder setBrightnessEvent(BrightnessEvent brightnessEvent) {
+            mBrightnessEvent = brightnessEvent;
+            return this;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 404c3ad..cfdb75f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -83,7 +83,7 @@
 import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.brightness.DisplayBrightnessController;
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
-import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
 import com.android.server.display.config.HysteresisLevels;
@@ -440,7 +440,7 @@
 
     // Responsible for evaluating and tracking the automatic brightness relevant states.
     // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
-    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+    private final AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy;
 
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -1337,9 +1337,6 @@
                     ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
         }
 
-        final boolean userSetBrightnessChanged = mDisplayBrightnessController
-                .updateUserSetScreenBrightness();
-
         DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
                 .updateBrightness(mPowerRequest, state);
         float brightnessState = displayBrightnessState.getBrightness();
@@ -1348,7 +1345,11 @@
         boolean slowChange = displayBrightnessState.isSlowChange();
         // custom transition duration
         float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
-
+        final boolean userSetBrightnessChanged =
+                mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated();
+        if (displayBrightnessState.getBrightnessEvent() != null) {
+            mTempBrightnessEvent.copyFrom(displayBrightnessState.getBrightnessEvent());
+        }
         // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
         // doesn't yet have a valid lux value to use with auto-brightness.
         if (mScreenOffBrightnessSensorController != null) {
@@ -1364,11 +1365,13 @@
         // request changes.
         final boolean wasShortTermModelActive =
                 mAutomaticBrightnessStrategy.isShortTermModelActive();
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
-                mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
-                mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
-                mDisplayBrightnessController.getLastUserSetScreenBrightness(),
-                userSetBrightnessChanged);
+        if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+            mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+                    mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+                    mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+                    mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+                    userSetBrightnessChanged);
+        }
 
         // If the brightness is already set then it's been overridden by something other than the
         // user, or is a temporary adjustment.
@@ -1390,9 +1393,22 @@
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
         int brightnessAdjustmentFlags = 0;
+        // All the conditions inside this if block will be moved to AutomaticBrightnessStrategy
+        if (mFlags.isRefactorDisplayPowerControllerEnabled()
+                && displayBrightnessState.getBrightnessReason().getReason()
+                        == BrightnessReason.REASON_AUTOMATIC) {
+            brightnessAdjustmentFlags =
+                    mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+            updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+            if (mScreenOffBrightnessSensorController != null) {
+                mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+            }
+            setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        }
         // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
-        if (Float.isNaN(brightnessState)
-                || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
+        if (!mFlags.isRefactorDisplayPowerControllerEnabled() && (Float.isNaN(brightnessState)
+                || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD)) {
             if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
                 brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
                         mTempBrightnessEvent);
@@ -1422,8 +1438,8 @@
             }
         } else {
             // Any non-auto-brightness values such as override or temporary should still be subject
-            // to clamping so that they don't go beyond the current max as specified by HBM
-            // Controller.
+            // to clamping so that they don't go beyond the current max as specified by Brightness
+            // Range Controller.
             brightnessState = clampScreenBrightness(brightnessState);
             mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index a12d248..b24caf4 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -222,7 +222,7 @@
         } else {
             // As external display is enabled by default, need to disable it now.
             // TODO(b/292196201) Remove when the display can be disabled before DPC is created.
-            logicalDisplay.setEnabledLocked(false);
+            mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, false);
         }
 
         if (!isExternalDisplayAllowed()) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 1dfe037..182b05a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -82,11 +82,8 @@
     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
     // Min and max strengths for even dimmer feature.
     private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
-    private static final float EVEN_DIMMER_MAX_STRENGTH = 70.0f; // not too dim yet.
+    private static final float EVEN_DIMMER_MAX_STRENGTH = 90.0f;
     private static final float BRIGHTNESS_MIN = 0.0f;
-    // The brightness at which we start using color matrices rather than backlight,
-    // to dim the display
-    private static final float BACKLIGHT_COLOR_TRANSITION_POINT = 0.1f;
 
     private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
 
@@ -995,9 +992,12 @@
 
                     private void applyColorMatrixBasedDimming(float brightnessState) {
                         int strength = (int) (MathUtils.constrainedMap(
-                                EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH, // to this range
-                                BRIGHTNESS_MIN, BACKLIGHT_COLOR_TRANSITION_POINT, // from this range
-                                brightnessState) + 0.5); // map this (+ rounded up)
+                                // to this range:
+                                EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH,
+                                // from this range:
+                                BRIGHTNESS_MIN, mDisplayDeviceConfig.getEvenDimmerTransitionPoint(),
+                                // map this (+ rounded up):
+                                brightnessState) + 0.5);
 
                         if (mEvenDimmerStrength < 0 // uninitialised
                                 || MathUtils.abs(mEvenDimmerStrength - strength) > 1
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 8084685..8b3e4a4 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -31,7 +31,7 @@
 import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.DisplayBrightnessState;
-import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.feature.DisplayManagerFlags;
 
@@ -76,6 +76,10 @@
     @GuardedBy("mLock")
     private float mLastUserSetScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
+    // Represents if the system has adjusted the brightness based on the user suggested value. Will
+    // be false if the brightness change is coming from a non-user source
+    private boolean mUserSetScreenBrightnessUpdated;
+
     // The listener which is to be notified everytime there is a change in the brightness in the
     // BrightnessSetting.
     private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
@@ -138,7 +142,6 @@
     public DisplayBrightnessState updateBrightness(
             DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
             int targetDisplayState) {
-
         DisplayBrightnessState state;
         synchronized (mLock) {
             mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
@@ -246,28 +249,14 @@
     }
 
     /**
-     * We want to return true if the user has set the screen brightness.
-     * RBC on, off, and intensity changes will return false.
-     * Slider interactions whilst in RBC will return true, just as when in non-rbc.
+     * Returns if the system has adjusted the brightness based on the user suggested value. Will
+     * be false if the brightness change is coming from a non-user source.
+     *
+     * Todo: 294444204 This is a temporary workaround, and should be moved to the manual brightness
+     * strategy once that is introduced
      */
-    public boolean updateUserSetScreenBrightness() {
-        synchronized (mLock) {
-            if (!BrightnessUtils.isValidBrightnessValue(mPendingScreenBrightness)) {
-                return false;
-            }
-            if (mCurrentScreenBrightness == mPendingScreenBrightness) {
-                mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-                setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-                return false;
-            }
-            setCurrentScreenBrightnessLocked(mPendingScreenBrightness);
-            mLastUserSetScreenBrightness = mPendingScreenBrightness;
-            mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        }
-        notifyCurrentScreenBrightness();
-        return true;
-
+    public boolean getIsUserSetScreenBrightnessUpdated() {
+        return mUserSetScreenBrightnessUpdated;
     }
 
     /**
@@ -355,7 +344,7 @@
     /**
      * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
      */
-    public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+    public AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy() {
         return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
     }
 
@@ -442,6 +431,33 @@
         }
     }
 
+    /**
+     * We want to return true if the user has set the screen brightness.
+     * RBC on, off, and intensity changes will return false.
+     * Slider interactions whilst in RBC will return true, just as when in non-rbc.
+     */
+    @VisibleForTesting
+    boolean updateUserSetScreenBrightness() {
+        mUserSetScreenBrightnessUpdated = false;
+        synchronized (mLock) {
+            if (!BrightnessUtils.isValidBrightnessValue(mPendingScreenBrightness)) {
+                return false;
+            }
+            if (mCurrentScreenBrightness == mPendingScreenBrightness) {
+                mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+                return false;
+            }
+            setCurrentScreenBrightnessLocked(mPendingScreenBrightness);
+            mLastUserSetScreenBrightness = mPendingScreenBrightness;
+            mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        }
+        notifyCurrentScreenBrightness();
+        mUserSetScreenBrightnessUpdated = true;
+        return true;
+    }
+
     @VisibleForTesting
     static class Injector {
         DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
@@ -470,7 +486,7 @@
      * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
      */
     private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
-        AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+        AutomaticBrightnessStrategy2 autoStrat = getAutomaticBrightnessStrategy();
 
         DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
         builder.setShouldUseAutoBrightness(
@@ -526,6 +542,12 @@
     private StrategySelectionRequest constructStrategySelectionRequest(
             DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
             int targetDisplayState) {
-        return new StrategySelectionRequest(displayPowerRequest, targetDisplayState);
+        boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+        float lastUserSetScreenBrightness;
+        synchronized (mLock) {
+            lastUserSetScreenBrightness = mLastUserSetScreenBrightness;
+        }
+        return new StrategySelectionRequest(displayPowerRequest, targetDisplayState,
+                lastUserSetScreenBrightness, userSetBrightnessChanged);
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 165c24b..da66879 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -27,6 +27,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -65,7 +66,16 @@
     // The brightness strategy used to manage the brightness state when the request is invalid.
     private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
     // Controls brightness when automatic (adaptive) brightness is running.
-    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+    private final AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy;
+
+    // The automatic strategy which controls the brightness when adaptive mode is ON.
+    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy1;
+
+    // The deprecated AutomaticBrightnessStrategy. Avoid using it for any new features without
+    // consulting with the display frameworks team. Use {@link AutomaticBrightnessStrategy} instead.
+    // This will be removed once the flag
+    // {@link DisplayManagerFlags#isRefactorDisplayPowerControllerEnabled is fully rolled out
+    private final AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy2;
     // Controls the brightness if adaptive brightness is on and there exists an active offload
     // session. Brightness value is provided by the offload session.
     @Nullable
@@ -101,7 +111,15 @@
         mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
         mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
         mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
-        mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
+        mAutomaticBrightnessStrategy1 =
+                (!mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
+                        : injector.getAutomaticBrightnessStrategy1(context, displayId);
+        mAutomaticBrightnessStrategy2 =
+                (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
+                        : injector.getAutomaticBrightnessStrategy2(context, displayId);
+        mAutomaticBrightnessStrategy =
+                (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled())
+                        ? mAutomaticBrightnessStrategy1 : mAutomaticBrightnessStrategy2;
         if (flags.isDisplayOffloadEnabled()) {
             mOffloadBrightnessStrategy = injector.getOffloadBrightnessStrategy();
         } else {
@@ -110,7 +128,7 @@
         mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
                 mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
                 mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
-                mOffloadBrightnessStrategy};
+                mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy};
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -142,6 +160,9 @@
         } else if (BrightnessUtils.isValidBrightnessValue(
                 mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
             displayBrightnessStrategy = mTemporaryBrightnessStrategy;
+        } else if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()
+                && isAutomaticBrightnessStrategyValid(strategySelectionRequest)) {
+            displayBrightnessStrategy = mAutomaticBrightnessStrategy1;
         } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
                 && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
                 mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
@@ -149,7 +170,8 @@
         }
 
         if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
-            postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy));
+            postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy,
+                    strategySelectionRequest));
         }
 
         if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
@@ -170,7 +192,7 @@
         return mFollowerBrightnessStrategy;
     }
 
-    public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+    public AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy() {
         return mAutomaticBrightnessStrategy;
     }
 
@@ -206,9 +228,28 @@
         }
     }
 
+    private boolean isAutomaticBrightnessStrategyValid(
+            StrategySelectionRequest strategySelectionRequest) {
+        mAutomaticBrightnessStrategy1.setAutoBrightnessState(
+                strategySelectionRequest.getTargetDisplayState(),
+                mAllowAutoBrightnessWhileDozingConfig,
+                BrightnessReason.REASON_UNKNOWN,
+                strategySelectionRequest.getDisplayPowerRequest().policy,
+                strategySelectionRequest.getLastUserSetScreenBrightness(),
+                strategySelectionRequest.isUserSetBrightnessChanged());
+        return mAutomaticBrightnessStrategy1.isAutoBrightnessValid();
+    }
+
     private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
-            DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) {
-        return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy);
+            DisplayBrightnessStrategy selectedDisplayBrightnessStrategy,
+            StrategySelectionRequest strategySelectionRequest) {
+        return new StrategySelectionNotifyRequest(
+                        strategySelectionRequest.getDisplayPowerRequest(),
+                strategySelectionRequest.getTargetDisplayState(),
+                selectedDisplayBrightnessStrategy,
+                strategySelectionRequest.getLastUserSetScreenBrightness(),
+                strategySelectionRequest.isUserSetBrightnessChanged(),
+                isAllowAutoBrightnessWhileDozingConfig());
     }
 
     private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
@@ -263,10 +304,16 @@
             return new InvalidBrightnessStrategy();
         }
 
-        AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+        AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
+                int displayId) {
             return new AutomaticBrightnessStrategy(context, displayId);
         }
 
+        AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context,
+                int displayId) {
+            return new AutomaticBrightnessStrategy2(context, displayId);
+        }
+
         OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
             return new OffloadBrightnessStrategy();
         }
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
index d8bd2e4..6e6c972 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.brightness;
 
+import android.hardware.display.DisplayManagerInternal;
+
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 
 import java.util.Objects;
@@ -25,11 +27,36 @@
  * DisplayBrightnessStrategy
  */
 public final class StrategySelectionNotifyRequest {
+    // The request to change the associated display's state and brightness
+    private DisplayManagerInternal.DisplayPowerRequest mDisplayPowerRequest;
+
+    // The display state to which the screen is switching to
+    private int mTargetDisplayState;
+
     // The strategy that was selected with the current request
     private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy;
 
-    public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) {
+    // The last brightness that was set by the user and not temporary. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+    private float mLastUserSetScreenBrightness;
+
+    // Represents if the user set screen brightness was changed or not.
+    private boolean mUserSetBrightnessChanged;
+
+    // True if light sensor is to be used to automatically determine doze screen brightness.
+    private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+    public StrategySelectionNotifyRequest(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState,
+            DisplayBrightnessStrategy displayBrightnessStrategy,
+            float lastUserSetScreenBrightness,
+            boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig) {
+        mDisplayPowerRequest = displayPowerRequest;
+        mTargetDisplayState = targetDisplayState;
         mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
+        mLastUserSetScreenBrightness = lastUserSetScreenBrightness;
+        mUserSetBrightnessChanged = userSetBrightnessChanged;
+        mAllowAutoBrightnessWhileDozingConfig = allowAutoBrightnessWhileDozingConfig;
     }
 
     public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
@@ -43,11 +70,52 @@
         }
         StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj;
         return other.getSelectedDisplayBrightnessStrategy()
-                == getSelectedDisplayBrightnessStrategy();
+                == getSelectedDisplayBrightnessStrategy()
+                && Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
+                && mTargetDisplayState == other.getTargetDisplayState()
+                && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged()
+                && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
+                && mAllowAutoBrightnessWhileDozingConfig
+                == other.isAllowAutoBrightnessWhileDozingConfig();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSelectedDisplayBrightnessStrategy);
+        return Objects.hash(mSelectedDisplayBrightnessStrategy, mDisplayPowerRequest,
+                mTargetDisplayState, mUserSetBrightnessChanged, mLastUserSetScreenBrightness,
+                mAllowAutoBrightnessWhileDozingConfig);
+    }
+
+    public float getLastUserSetScreenBrightness() {
+        return mLastUserSetScreenBrightness;
+    }
+
+    public boolean isUserSetBrightnessChanged() {
+        return mUserSetBrightnessChanged;
+    }
+
+    public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
+        return mDisplayPowerRequest;
+    }
+
+    public int getTargetDisplayState() {
+        return mTargetDisplayState;
+    }
+
+    public boolean isAllowAutoBrightnessWhileDozingConfig() {
+        return mAllowAutoBrightnessWhileDozingConfig;
+    }
+
+    /**
+     * A utility to stringify a StrategySelectionNotifyRequest
+     */
+    public String toString() {
+        return "StrategySelectionNotifyRequest:"
+                + " mDisplayPowerRequest=" + mDisplayPowerRequest
+                + " mTargetDisplayState=" + mTargetDisplayState
+                + " mSelectedDisplayBrightnessStrategy=" + mSelectedDisplayBrightnessStrategy
+                + " mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness
+                + " mUserSetBrightnessChanged=" + mUserSetBrightnessChanged
+                + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig;
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
index e618596..ae745efc 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
@@ -31,10 +31,20 @@
     // The display state to which the screen is switching to
     private int mTargetDisplayState;
 
+    // The last brightness that was set by the user and not temporary. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+    private float mLastUserSetScreenBrightness;
+
+    // Represents if the user set screen brightness was changed or not.
+    private boolean mUserSetBrightnessChanged;
+
     public StrategySelectionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
-            int targetDisplayState) {
+            int targetDisplayState, float lastUserSetScreenBrightness,
+            boolean userSetBrightnessChanged) {
         mDisplayPowerRequest = displayPowerRequest;
         mTargetDisplayState = targetDisplayState;
+        mLastUserSetScreenBrightness = lastUserSetScreenBrightness;
+        mUserSetBrightnessChanged = userSetBrightnessChanged;
     }
 
     public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -45,18 +55,30 @@
         return mTargetDisplayState;
     }
 
+
+    public float getLastUserSetScreenBrightness() {
+        return mLastUserSetScreenBrightness;
+    }
+
+    public boolean isUserSetBrightnessChanged() {
+        return mUserSetBrightnessChanged;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof StrategySelectionRequest)) {
             return false;
         }
         StrategySelectionRequest other = (StrategySelectionRequest) obj;
-        return Objects.equals(other.getDisplayPowerRequest(), getDisplayPowerRequest())
-                && other.getTargetDisplayState() == getTargetDisplayState();
+        return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
+                && mTargetDisplayState == other.getTargetDisplayState()
+                && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
+                && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDisplayPowerRequest, mTargetDisplayState);
+        return Objects.hash(mDisplayPowerRequest, mTargetDisplayState,
+                mLastUserSetScreenBrightness, mUserSetBrightnessChanged);
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 08d4cfd..4be7332 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -27,9 +28,11 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -40,7 +43,8 @@
  * that it is being executed from the power thread, and hence doesn't synchronize
  * any of its resources
  */
-public class AutomaticBrightnessStrategy {
+public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2
+        implements DisplayBrightnessStrategy{
     private final Context mContext;
     // The DisplayId of the associated logical display
     private final int mDisplayId;
@@ -88,7 +92,12 @@
     @Nullable
     private BrightnessConfiguration mBrightnessConfiguration;
 
+    // Indicates if the strategy is already configured for a request, in which case we wouldn't
+    // want to re-evaluate the auto-brightness state
+    private boolean mIsConfigured;
+
     public AutomaticBrightnessStrategy(Context context, int displayId) {
+        super(context, displayId);
         mContext = context;
         mDisplayId = displayId;
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
@@ -112,7 +121,7 @@
         mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
                 && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = mIsAutoBrightnessEnabled
-                    && brightnessReason != BrightnessReason.REASON_FOLLOWER
+                && brightnessReason != BrightnessReason.REASON_FOLLOWER
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
                 : mAutoBrightnessDisabledDueToDisplayOff
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
@@ -120,12 +129,36 @@
 
         accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
                 policy, targetDisplayState, mBrightnessConfiguration, autoBrightnessState);
+        mIsConfigured = true;
+    }
+
+    public void setIsConfigured(boolean configure) {
+        mIsConfigured = configure;
     }
 
     public boolean isAutoBrightnessEnabled() {
         return mIsAutoBrightnessEnabled;
     }
 
+    /**
+     * Validates if the auto-brightness strategy is valid or not considering the current system
+     * state.
+     */
+    public boolean isAutoBrightnessValid() {
+        boolean isValid = false;
+        if (isAutoBrightnessEnabled()) {
+            float brightness = (mAutomaticBrightnessController != null)
+                    ? mAutomaticBrightnessController.getAutomaticScreenBrightness(null)
+                    : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            if (BrightnessUtils.isValidBrightnessValue(brightness)
+                    || brightness == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+                isValid = true;
+            }
+        }
+        setAutoBrightnessApplied(isValid);
+        return isValid;
+    }
+
     public boolean isAutoBrightnessDisabledDueToDisplayOff() {
         return mAutoBrightnessDisabledDueToDisplayOff;
     }
@@ -217,6 +250,29 @@
         mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
     }
 
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+        BrightnessEvent brightnessEvent = new BrightnessEvent(mDisplayId);
+        float brightness = getAutomaticScreenBrightness(brightnessEvent);
+        return new DisplayBrightnessState.Builder()
+                .setBrightness(brightness)
+                .setSdrBrightness(brightness)
+                .setBrightnessReason(brightnessReason)
+                .setDisplayBrightnessStrategyName(getName())
+                .setIsSlowChange(hasAppliedAutoBrightness()
+                        && !getAutoBrightnessAdjustmentChanged())
+                .setBrightnessEvent(brightnessEvent)
+                .build();
+    }
+
+    @Override
+    public String getName() {
+        return "AutomaticBrightnessStrategy";
+    }
+
     /**
      * Dumps the state of this class.
      */
@@ -238,6 +294,26 @@
                 + mAutoBrightnessAdjustmentReasonsFlags);
     }
 
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        if (!mIsConfigured) {
+            setAutoBrightnessState(strategySelectionNotifyRequest.getTargetDisplayState(),
+                    strategySelectionNotifyRequest.isAllowAutoBrightnessWhileDozingConfig(),
+                    strategySelectionNotifyRequest.getSelectedDisplayBrightnessStrategy()
+                            .getReason(),
+                    strategySelectionNotifyRequest.getDisplayPowerRequest().policy,
+                    strategySelectionNotifyRequest.getLastUserSetScreenBrightness(),
+                    strategySelectionNotifyRequest.isUserSetBrightnessChanged());
+        }
+        mIsConfigured = false;
+    }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_AUTOMATIC;
+    }
+
     /**
      * Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
      * set.
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
new file mode 100644
index 0000000..25e8b23
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.display.brightness.strategy;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import java.io.PrintWriter;
+
+/**
+ * Helps manage the brightness based on the ambient environment (Ambient Light/lux sensor) using
+ * mappings from lux to nits to brightness, configured in the
+ * {@link com.android.server.display.DisplayDeviceConfig} class. This class inherently assumes
+ * that it is being executed from the power thread, and hence doesn't synchronize
+ * any of its resources
+ *
+ * @deprecated This class is relevant only while the
+ * {@link DisplayManagerFlags#isRefactorDisplayPowerControllerEnabled()} is not fully rolled out.
+ * Till then, please replicated your changes to {@link AutomaticBrightnessStrategy} as well.
+ */
+@Deprecated
+public class AutomaticBrightnessStrategy2 {
+    private final Context mContext;
+    // The DisplayId of the associated logical display
+    private final int mDisplayId;
+    // The last auto brightness adjustment that was set by the user and is not temporary. Set to
+    // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+    private float mAutoBrightnessAdjustment;
+    // The pending auto brightness adjustment that will take effect on the next power state update.
+    private float mPendingAutoBrightnessAdjustment;
+    // The temporary auto brightness adjustment. This was historically used when a user interacts
+    // with the adjustment slider but hasn't settled on a choice yet.
+    // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+    private float mTemporaryAutoBrightnessAdjustment;
+    // Indicates if the temporary auto brightness adjustment has been applied while updating the
+    // associated display brightness
+    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+    // Indicates if the auto brightness adjustment has happened.
+    private boolean mAutoBrightnessAdjustmentChanged;
+    // Indicates the reasons for the auto-brightness adjustment
+    private int mAutoBrightnessAdjustmentReasonsFlags = 0;
+    // Indicates if the short term model should be reset before fetching the new brightness
+    // Todo(273543270): Short term model is an internal information of
+    //  AutomaticBrightnessController and shouldn't be exposed outside of that class
+    private boolean mShouldResetShortTermModel = false;
+    // Remembers whether the auto-brightness has been applied in the latest brightness update.
+    private boolean mAppliedAutoBrightness = false;
+    // The controller for the automatic brightness level.
+    @Nullable
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+    // The system setting denoting if the auto-brightness for the current user is enabled or not
+    private boolean mUseAutoBrightness = false;
+    // Indicates if the auto-brightness is currently enabled or not. It's possible that even if
+    // the user has enabled the auto-brightness from the settings, it is disabled because the
+    // display is off
+    private boolean mIsAutoBrightnessEnabled = false;
+    // Indicates if auto-brightness is disabled due to the display being off. Needed for metric
+    // purposes.
+    private boolean mAutoBrightnessDisabledDueToDisplayOff;
+    // If the auto-brightness model for the last manual changes done by the user.
+    private boolean mIsShortTermModelActive = false;
+
+    // The BrightnessConfiguration currently being used
+    // Todo(273543270): BrightnessConfiguration is an internal implementation detail of
+    //  AutomaticBrightnessController, and AutomaticBrightnessStrategy shouldn't be aware of its
+    //  existence.
+    @Nullable
+    private BrightnessConfiguration mBrightnessConfiguration;
+
+    public AutomaticBrightnessStrategy2(Context context, int displayId) {
+        mContext = context;
+        mDisplayId = displayId;
+        mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    }
+
+    /**
+     * Sets up the automatic brightness states of this class. Also configures
+     * AutomaticBrightnessController accounting for any manual changes made by the user.
+     */
+    public void setAutoBrightnessState(int targetDisplayState,
+            boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
+            float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+        final boolean autoBrightnessEnabledInDoze =
+                allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
+        mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
+                && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+                && brightnessReason != BrightnessReason.REASON_OVERRIDE
+                && mAutomaticBrightnessController != null;
+        mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+                && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+        final int autoBrightnessState = mIsAutoBrightnessEnabled
+                && brightnessReason != BrightnessReason.REASON_FOLLOWER
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : mAutoBrightnessDisabledDueToDisplayOff
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+        accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
+                policy, targetDisplayState, mBrightnessConfiguration, autoBrightnessState);
+    }
+
+    public boolean isAutoBrightnessEnabled() {
+        return mIsAutoBrightnessEnabled;
+    }
+
+    public boolean isAutoBrightnessDisabledDueToDisplayOff() {
+        return mAutoBrightnessDisabledDueToDisplayOff;
+    }
+
+    /**
+     * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
+     * display.
+     */
+    public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration,
+            boolean shouldResetShortTermModel) {
+        mBrightnessConfiguration = brightnessConfiguration;
+        setShouldResetShortTermModel(shouldResetShortTermModel);
+    }
+
+    /**
+     * Promotes the pending auto-brightness adjustments which are yet to be applied to the current
+     * adjustments. Note that this is not applying the new adjustments to the AutoBrightness mapping
+     * strategies, but is only accommodating the changes in this class.
+     */
+    public boolean processPendingAutoBrightnessAdjustments() {
+        mAutoBrightnessAdjustmentChanged = false;
+        if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+            return false;
+        }
+        if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+            mPendingAutoBrightnessAdjustment = Float.NaN;
+            return false;
+        }
+        mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+        mPendingAutoBrightnessAdjustment = Float.NaN;
+        mTemporaryAutoBrightnessAdjustment = Float.NaN;
+        mAutoBrightnessAdjustmentChanged = true;
+        return true;
+    }
+
+    /**
+     * Updates the associated AutomaticBrightnessController
+     */
+    public void setAutomaticBrightnessController(
+            AutomaticBrightnessController automaticBrightnessController) {
+        if (automaticBrightnessController == mAutomaticBrightnessController) {
+            return;
+        }
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.stop();
+        }
+        mAutomaticBrightnessController = automaticBrightnessController;
+    }
+
+    /**
+     * Returns if the auto-brightness of the associated display has been enabled or not
+     */
+    public boolean shouldUseAutoBrightness() {
+        return mUseAutoBrightness;
+    }
+
+    /**
+     * Sets the auto-brightness state of the associated display. Called when the user makes a change
+     * in the system setting to enable/disable the auto-brightness.
+     */
+    public void setUseAutoBrightness(boolean useAutoBrightness) {
+        mUseAutoBrightness = useAutoBrightness;
+    }
+
+    /**
+     * Returns if the user made brightness change events(Typically when they interact with the
+     * brightness slider) were accommodated in the auto-brightness mapping strategies. This doesn't
+     * account for the latest changes that have been made by the user.
+     */
+    public boolean isShortTermModelActive() {
+        return mIsShortTermModelActive;
+    }
+
+    /**
+     * Sets the pending auto-brightness adjustments in the system settings. Executed
+     * when there is a change in the brightness system setting, or when there is a user switch.
+     */
+    public void updatePendingAutoBrightnessAdjustments() {
+        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+        mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
+                : BrightnessUtils.clampBrightnessAdjustment(adj);
+    }
+
+    /**
+     * Sets the temporary auto-brightness adjustments
+     */
+    public void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+        mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
+    }
+
+    /**
+     * Dumps the state of this class.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("AutomaticBrightnessStrategy:");
+        writer.println("  mDisplayId=" + mDisplayId);
+        writer.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+        writer.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+        writer.println(
+                "  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+        writer.println("  mShouldResetShortTermModel=" + mShouldResetShortTermModel);
+        writer.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+        writer.println("  mAutoBrightnessAdjustmentChanged=" + mAutoBrightnessAdjustmentChanged);
+        writer.println("  mAppliedTemporaryAutoBrightnessAdjustment="
+                + mAppliedTemporaryAutoBrightnessAdjustment);
+        writer.println("  mUseAutoBrightness=" + mUseAutoBrightness);
+        writer.println("  mWasShortTermModelActive=" + mIsShortTermModelActive);
+        writer.println("  mAutoBrightnessAdjustmentReasonsFlags="
+                + mAutoBrightnessAdjustmentReasonsFlags);
+    }
+
+    /**
+     * Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
+     * set.
+     */
+    public boolean getAutoBrightnessAdjustmentChanged() {
+        return mAutoBrightnessAdjustmentChanged;
+    }
+
+    /**
+     * Returns whether the latest temporary auto-brightness adjustments have been applied or not
+     */
+    public boolean isTemporaryAutoBrightnessAdjustmentApplied() {
+        return mAppliedTemporaryAutoBrightnessAdjustment;
+    }
+
+    /**
+     * Evaluates the target automatic brightness of the associated display.
+     * @param brightnessEvent Event object to populate with details about why the specific
+     *                        brightness was chosen.
+     */
+    public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) {
+        float brightness = (mAutomaticBrightnessController != null)
+                ? mAutomaticBrightnessController.getAutomaticScreenBrightness(brightnessEvent)
+                : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        adjustAutomaticBrightnessStateIfValid(brightness);
+        return brightness;
+    }
+
+    /**
+     * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
+     * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
+     * the initial brightness in doze mode.
+     * @param brightnessEvent Event object to populate with details about why the specific
+     *                        brightness was chosen.
+     */
+    public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+            BrightnessEvent brightnessEvent) {
+        float brightness = (mAutomaticBrightnessController != null)
+                ? mAutomaticBrightnessController
+                .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent)
+                : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        adjustAutomaticBrightnessStateIfValid(brightness);
+        return brightness;
+    }
+
+    /**
+     * Gets the auto-brightness adjustment flag change reason
+     */
+    public int getAutoBrightnessAdjustmentReasonsFlags() {
+        return mAutoBrightnessAdjustmentReasonsFlags;
+    }
+
+    /**
+     * Returns if the auto brightness has been applied
+     */
+    public boolean hasAppliedAutoBrightness() {
+        return mAppliedAutoBrightness;
+    }
+
+    /**
+     * Used to adjust the state of this class when the automatic brightness value for the
+     * associated display is valid
+     */
+    @VisibleForTesting
+    void adjustAutomaticBrightnessStateIfValid(float brightnessState) {
+        mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
+                ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
+                : BrightnessReason.ADJUSTMENT_AUTO;
+        float newAutoBrightnessAdjustment =
+                (mAutomaticBrightnessController != null)
+                        ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
+                        : 0.0f;
+        if (!Float.isNaN(newAutoBrightnessAdjustment)
+                && mAutoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+            // If the auto-brightness controller has decided to change the adjustment value
+            // used, make sure that's reflected in settings.
+            putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+        } else {
+            mAutoBrightnessAdjustmentReasonsFlags = 0;
+        }
+    }
+
+    /**
+     * Sets up the system to reset the short term model. Note that this will not reset the model
+     * right away, but ensures that the reset happens whenever the next brightness change happens
+     */
+    @VisibleForTesting
+    void setShouldResetShortTermModel(boolean shouldResetShortTermModel) {
+        mShouldResetShortTermModel = shouldResetShortTermModel;
+    }
+
+    @VisibleForTesting
+    boolean shouldResetShortTermModel() {
+        return mShouldResetShortTermModel;
+    }
+
+    @VisibleForTesting
+    float getAutoBrightnessAdjustment() {
+        return mAutoBrightnessAdjustment;
+    }
+
+    @VisibleForTesting
+    float getPendingAutoBrightnessAdjustment() {
+        return mPendingAutoBrightnessAdjustment;
+    }
+
+    @VisibleForTesting
+    float getTemporaryAutoBrightnessAdjustment() {
+        return mTemporaryAutoBrightnessAdjustment;
+    }
+
+    @VisibleForTesting
+    void putAutoBrightnessAdjustmentSetting(float adjustment) {
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mAutoBrightnessAdjustment = adjustment;
+            Settings.System.putFloatForUser(mContext.getContentResolver(),
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
+    /**
+     * Sets if the auto-brightness is applied on the latest brightness change.
+     */
+    public void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+        mAppliedAutoBrightness = autoBrightnessApplied;
+    }
+
+    /**
+     * Accommodates the latest manual changes made by the user. Also updates {@link
+     * AutomaticBrightnessController} about the changes and configures it accordingly.
+     */
+    @VisibleForTesting
+    void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
+            float lastUserSetScreenBrightness, int policy, int displayState,
+            BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+        // Update the pending auto-brightness adjustments if any. This typically checks and adjusts
+        // the state of the class if the user moves the brightness slider and has settled to a
+        // different value
+        processPendingAutoBrightnessAdjustments();
+        // Update the temporary auto-brightness adjustments if any. This typically checks and
+        // adjusts the state of this class if the user is in the process of moving the brightness
+        // slider, but hasn't settled to any value yet
+        float autoBrightnessAdjustment = updateTemporaryAutoBrightnessAdjustments();
+        mIsShortTermModelActive = false;
+        // Configure auto-brightness.
+        if (mAutomaticBrightnessController != null) {
+            // Accommodate user changes if any in the auto-brightness model
+            mAutomaticBrightnessController.configure(autoBrightnessState,
+                    brightnessConfiguration,
+                    lastUserSetScreenBrightness,
+                    userSetBrightnessChanged, autoBrightnessAdjustment,
+                    mAutoBrightnessAdjustmentChanged, policy, displayState,
+                    mShouldResetShortTermModel);
+            mShouldResetShortTermModel = false;
+            // We take note if the user brightness point is still being used in the current
+            // auto-brightness model.
+            mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
+        }
+    }
+
+    /**
+     * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
+     * Temporary brightness adjustments happen when the user moves the brightness slider in the
+     * auto-brightness mode, but hasn't settled to a value yet
+     */
+    private float updateTemporaryAutoBrightnessAdjustments() {
+        mAppliedTemporaryAutoBrightnessAdjustment =
+                !Float.isNaN(mTemporaryAutoBrightnessAdjustment);
+        // We do not update the mAutoBrightnessAdjustment with mTemporaryAutoBrightnessAdjustment
+        // since we have not settled to a value yet
+        return mAppliedTemporaryAutoBrightnessAdjustment
+                ? mTemporaryAutoBrightnessAdjustment : mAutoBrightnessAdjustment;
+    }
+
+    /**
+     * Returns the auto-brightness adjustment that is set in the system setting.
+     */
+    private float getAutoBrightnessAdjustmentSetting() {
+        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+        return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampBrightnessAdjustment(adj);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index 11edde9..9c1acea 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -53,6 +53,11 @@
     }
 
     @Override
+    public int getReason() {
+        return BrightnessReason.REASON_BOOST;
+    }
+
+    @Override
     public void dump(PrintWriter writer) {}
 
     @Override
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 7b49957..61dd6d5 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -45,6 +45,11 @@
     String getName();
 
     /**
+     * Returns the reason for the change of the brightness
+     */
+    int getReason();
+
+    /**
      * Dumps the state of the Strategy
      * @param writer
      */
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 5afdc42..1f7efd1 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -53,4 +53,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_DOZE;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 0650c1c..baac276 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -89,4 +89,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_FOLLOWER;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index bf37ee0..4abd028 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -51,4 +51,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_UNKNOWN;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index d2bb1e2..64dc47c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -79,4 +79,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_OFFLOAD;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 653170c..9605a88 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -52,4 +52,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_OVERRIDE;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index f0cce23..c9dc298 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -53,4 +53,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_SCREEN_OFF;
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index 91e1d09..6a691d1 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -80,4 +80,9 @@
             StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
         // DO NOTHING
     }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_TEMPORARY;
+    }
 }
diff --git a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 5556365..7e2e10a 100644
--- a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -66,6 +66,10 @@
      * Spline, mapping between backlight and brightness
      */
     public final Spline mBacklightToBrightness;
+
+    /**
+     * Spline, mapping the minimum nits for each lux condition.
+     */
     public final Spline mMinLuxToNits;
 
     @VisibleForTesting
@@ -114,34 +118,35 @@
             return null;
         }
 
-        List<Float> nitsList = lbm.getNits();
-        List<Float> backlightList = lbm.getBacklight();
-        List<Float> brightnessList = lbm.getBrightness();
-        float transitionPoints = lbm.getTransitionPoint().floatValue();
+        ComprehensiveBrightnessMap map = lbm.getBrightnessMapping();
+        if (map == null) {
+            return null;
+        }
+        String interpolation = map.getInterpolation();
 
-        if (nitsList.isEmpty()
-                || backlightList.size() != brightnessList.size()
-                || backlightList.size() != nitsList.size()) {
-            Slog.e(TAG, "Invalid even dimmer array lengths");
+        List<BrightnessPoint> brightnessPoints = map.getBrightnessPoint();
+        if (brightnessPoints.isEmpty()) {
             return null;
         }
 
-        float[] nits = new float[nitsList.size()];
-        float[] backlight = new float[nitsList.size()];
-        float[] brightness = new float[nitsList.size()];
+        float[] nits = new float[brightnessPoints.size()];
+        float[] backlight = new float[brightnessPoints.size()];
+        float[] brightness = new float[brightnessPoints.size()];
 
-        for (int i = 0; i < nitsList.size(); i++) {
-            nits[i] = nitsList.get(i);
-            backlight[i] = backlightList.get(i);
-            brightness[i] = brightnessList.get(i);
+        for (int i = 0; i < brightnessPoints.size(); i++) {
+            BrightnessPoint val = brightnessPoints.get(i);
+            nits[i] = val.getNits().floatValue();
+            backlight[i] = val.getBacklight().floatValue();
+            brightness[i] = val.getBrightness().floatValue();
         }
 
-        final NitsMap map = lbm.getLuxToMinimumNitsMap();
-        if (map == null) {
+        float transitionPoint = lbm.getTransitionPoint().floatValue();
+        final NitsMap minimumNitsMap = lbm.getLuxToMinimumNitsMap();
+        if (minimumNitsMap == null) {
             Slog.e(TAG, "Invalid min lux to nits mapping");
             return null;
         }
-        final List<Point> points = map.getPoint();
+        final List<Point> points = minimumNitsMap.getPoint();
         final int size = points.size();
 
         float[] minLux = new float[size];
@@ -164,7 +169,18 @@
             ++i;
         }
 
-        return new EvenDimmerBrightnessData(transitionPoints, nits, backlight, brightness,
+        // Explicitly choose linear interpolation.
+        if ("linear".equals(interpolation)) {
+            return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
+                    new Spline.LinearSpline(backlight, nits),
+                    new Spline.LinearSpline(nits, backlight),
+                    new Spline.LinearSpline(brightness, backlight),
+                    new Spline.LinearSpline(backlight, brightness),
+                    new Spline.LinearSpline(minLux, minNits)
+            );
+        }
+
+        return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
                 Spline.createSpline(backlight, nits),
                 Spline.createSpline(nits, backlight),
                 Spline.createSpline(brightness, backlight),
diff --git a/services/core/java/com/android/server/flags/compaction.aconfig b/services/core/java/com/android/server/flags/compaction.aconfig
index 067a1c9..58cc560 100644
--- a/services/core/java/com/android/server/flags/compaction.aconfig
+++ b/services/core/java/com/android/server/flags/compaction.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.server.flags"
-container: "system"
 
 flag {
     name: "disable_system_compaction"
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
index 16a45cd..606a6be 100644
--- a/services/core/java/com/android/server/flags/pinner.aconfig
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.server.flags"
-container: "system"
 
 flag {
     name: "pin_webview"
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 8e0eb05..10b5eff 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -1,5 +1,4 @@
 package: "com.android.server.flags"
-container: "system"
 
 flag {
      namespace: "wear_frameworks"
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
index 5be0735..d494be5 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.grammaticalinflection;
 
 import android.app.backup.BackupManager;
+import android.content.AttributionSource;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -30,6 +31,7 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
 import java.time.Clock;
 import java.time.Duration;
 import java.util.HashMap;
@@ -47,6 +49,7 @@
     private final PackageManager mPackageManager;
     private final GrammaticalInflectionService mGrammaticalGenderService;
     private final Clock mClock;
+    private final AttributionSource mAttributionSource;
 
     static class StagedData {
         final long mCreationTimeMillis;
@@ -58,8 +61,9 @@
         }
     }
 
-    public GrammaticalInflectionBackupHelper(GrammaticalInflectionService grammaticalGenderService,
-            PackageManager packageManager) {
+    public GrammaticalInflectionBackupHelper(AttributionSource attributionSource,
+            GrammaticalInflectionService grammaticalGenderService, PackageManager packageManager) {
+        mAttributionSource = attributionSource;
         mGrammaticalGenderService = grammaticalGenderService;
         mPackageManager = packageManager;
         mClock = Clock.systemUTC();
@@ -115,6 +119,23 @@
         }
     }
 
+    /**
+     * Returns the system-gender to be backed up as a data-blob.
+     */
+    public byte[] getSystemBackupPayload(int userId) {
+        int gender = mGrammaticalGenderService.getSystemGrammaticalGender(mAttributionSource,
+                userId);
+        return intToByteArray(gender);
+    }
+
+    /**
+     * Restores the system-gender that were previously backed up.
+     */
+    public void applyRestoredSystemPayload(byte[] payload, int userId) {
+        int gender = convertByteArrayToInt(payload);
+        mGrammaticalGenderService.setSystemWideGrammaticalGender(gender, userId);
+    }
+
     private boolean hasSetBeforeRestoring(String pkgName, int userId) {
         return mGrammaticalGenderService.getApplicationGrammaticalGender(pkgName, userId)
                 != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -157,6 +178,17 @@
         }
     }
 
+    private byte[] intToByteArray(final int gender) {
+        ByteBuffer bb = ByteBuffer.allocate(4);
+        bb.putInt(gender);
+        return bb.array();
+    }
+
+    private int convertByteArrayToInt(byte[] intBytes) {
+        ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
+        return byteBuffer.getInt();
+    }
+
     private HashMap<String, Integer> readFromByteArray(byte[] payload) {
         HashMap<String, Integer> data = new HashMap<>();
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index c2c82ed..2816d08 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -62,5 +62,16 @@
      * Whether the package can get the system grammatical gender or not.
      */
     public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
+
+
+    /**
+     * Returns the system-gender to be backed up as a data-blob.
+     */
+    public abstract @Nullable byte[] getSystemBackupPayload(int userId);
+
+    /**
+     * Restores the system-gender that were previously backed up.
+     */
+    public abstract void applyRestoredSystemPayload(byte[] payload, int userId);
 }
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 96d4bda..93a71b9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -102,7 +102,8 @@
         mContext = context;
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mBackupHelper = new GrammaticalInflectionBackupHelper(this, context.getPackageManager());
+        mBackupHelper = new GrammaticalInflectionBackupHelper(mContext.getAttributionSource(), this,
+                context.getPackageManager());
         mBinderService = new GrammaticalInflectionBinderService();
         mPermissionManager = context.getSystemService(PermissionManager.class);
     }
@@ -176,6 +177,18 @@
         }
 
         @Override
+        @Nullable
+        public byte[] getSystemBackupPayload(int userId) {
+            isCallerAllowed();
+            return mBackupHelper.getSystemBackupPayload(userId);
+        }
+
+        @Override
+        public void applyRestoredSystemPayload(byte[] payload, int userId) {
+            mBackupHelper.applyRestoredSystemPayload(payload, userId);
+        }
+
+        @Override
         public int getSystemGrammaticalGender(int userId) {
             return checkSystemTermsOfAddressIsEnabled()
                     ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
@@ -290,6 +303,7 @@
                     userId,
                     grammaticalGender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
                     preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+            GrammaticalInflectionBackupHelper.notifyBackupManager();
         } catch (RemoteException e) {
             Log.w(TAG, "Can not update configuration", e);
         }
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 9d04682..ea240c7 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,7 +196,12 @@
                 File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
                 if (!signatureFile.exists()) {
                     Slog.i(TAG, "The signature file is missing.");
-                    return;
+                    if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+                        return;
+                    } else {
+                        FileUtils.deleteContentsAndDir(dir);
+                        continue;
+                    }
                 }
                 byte[] signature;
                 try {
@@ -221,33 +226,39 @@
 
                 FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
                 if (fontConfig == null) {
-                    // Use preinstalled font config for checking revision number.
-                    fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+                    if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+                        // Use preinstalled font config for checking revision number.
+                        fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+                    } else {
+                        fontConfig = getSystemFontConfig();
+                    }
                 }
                 addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
             }
 
-            // Treat as error if post script name of font family was not installed.
-            for (int i = 0; i < config.fontFamilies.size(); ++i) {
-                FontUpdateRequest.Family family = config.fontFamilies.get(i);
-                for (int j = 0; j < family.getFonts().size(); ++j) {
-                    FontUpdateRequest.Font font = family.getFonts().get(j);
-                    if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
-                        continue;
-                    }
+            if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+                // Treat as error if post script name of font family was not installed.
+                for (int i = 0; i < config.fontFamilies.size(); ++i) {
+                    FontUpdateRequest.Family family = config.fontFamilies.get(i);
+                    for (int j = 0; j < family.getFonts().size(); ++j) {
+                        FontUpdateRequest.Font font = family.getFonts().get(j);
+                        if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+                            continue;
+                        }
 
-                    if (fontConfig == null) {
-                        fontConfig = mConfigSupplier.apply(Collections.emptyMap());
-                    }
+                        if (fontConfig == null) {
+                            fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+                        }
 
-                    if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
-                        continue;
-                    }
+                        if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+                            continue;
+                        }
 
-                    Slog.e(TAG, "Unknown font that has PostScript name "
-                            + font.getPostScriptName() + " is requested in FontFamily "
-                            + family.getName());
-                    return;
+                        Slog.e(TAG, "Unknown font that has PostScript name "
+                                + font.getPostScriptName() + " is requested in FontFamily "
+                                + family.getName());
+                        return;
+                    }
                 }
             }
 
@@ -262,7 +273,9 @@
                 mFontFileInfoMap.clear();
                 mLastModifiedMillis = 0;
                 FileUtils.deleteContents(mFilesDir);
-                mConfigFile.delete();
+                if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+                    mConfigFile.delete();
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 77119d5..f32c11d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1197,54 +1197,11 @@
     }
 
     @Override // Binder call
-    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
-            final InputDeviceIdentifier identifier) {
-        return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier);
-    }
-
-    @Override // Binder call
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
         return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor);
     }
 
     @Override // Binder call
-    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
-        return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier);
-    }
-
-    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
-    @Override // Binder call
-    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
-        super.setCurrentKeyboardLayoutForInputDevice_enforcePermission();
-        mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier,
-                keyboardLayoutDescriptor);
-    }
-
-    @Override // Binder call
-    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
-        return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier);
-    }
-
-    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
-    @Override // Binder call
-    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
-        super.addKeyboardLayoutForInputDevice_enforcePermission();
-        mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier,
-                keyboardLayoutDescriptor);
-    }
-
-    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
-    @Override // Binder call
-    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
-        super.removeKeyboardLayoutForInputDevice_enforcePermission();
-        mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier,
-                keyboardLayoutDescriptor);
-    }
-
-    @Override // Binder call
     public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
             InputDeviceIdentifier identifier, @UserIdInt int userId,
             @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
@@ -1270,11 +1227,6 @@
                 imeInfo, imeSubtype);
     }
 
-
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
-    }
-
     public void setFocusedApplication(int displayId, InputApplicationHandle application) {
         mNative.setFocusedApplication(displayId, application);
     }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 6610081..9ba647f 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -60,7 +60,6 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -69,7 +68,6 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -96,7 +94,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.stream.Stream;
 
 /**
  * A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts.
@@ -112,9 +109,8 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
-    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
-    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
-    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2;
+    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3;
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -126,13 +122,14 @@
     // Connected keyboards with associated keyboard layouts (either auto-detected or manually
     // selected layout).
     private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
-    private Toast mSwitchedKeyboardLayoutToast;
 
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
     private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
             new ArrayMap<>();
+
+    private HashSet<String> mAvailableLayouts = new HashSet<>();
     private final Object mImeInfoLock = new Object();
     @Nullable
     @GuardedBy("mImeInfoLock")
@@ -206,68 +203,51 @@
         }
 
         boolean needToShowNotification = false;
-        if (!useNewSettingsUi()) {
-            synchronized (mDataStore) {
-                String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
-                if (layout == null) {
-                    layout = getDefaultKeyboardLayout(inputDevice);
-                    if (layout != null) {
-                        setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
-                    }
-                }
-                if (layout == null) {
-                    // In old settings show notification always until user manually selects a
-                    // layout in the settings.
+        Set<String> selectedLayouts = new HashSet<>();
+        List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
+        List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
+        boolean hasMissingLayout = false;
+        for (ImeInfo imeInfo : imeInfoList) {
+            // Check if the layout has been previously configured
+            KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+                    keyboardIdentifier, imeInfo);
+            if (result.getLayoutDescriptor() != null) {
+                selectedLayouts.add(result.getLayoutDescriptor());
+            } else {
+                hasMissingLayout = true;
+            }
+            resultList.add(result);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "Layouts selected for input device: " + keyboardIdentifier
+                            + " -> selectedLayouts: " + selectedLayouts);
+        }
+
+        // If even one layout not configured properly, we need to ask user to configure
+        // the keyboard properly from the Settings.
+        if (hasMissingLayout) {
+            selectedLayouts.clear();
+        }
+
+        config.setConfiguredLayouts(selectedLayouts);
+
+        synchronized (mDataStore) {
+            try {
+                final String key = keyboardIdentifier.toString();
+                if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+                    // Need to show the notification only if layout selection changed
+                    // from the previous configuration
                     needToShowNotification = true;
                 }
-            }
-        } else {
-            Set<String> selectedLayouts = new HashSet<>();
-            List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
-            List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
-            boolean hasMissingLayout = false;
-            for (ImeInfo imeInfo : imeInfoList) {
-                // Check if the layout has been previously configured
-                KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
-                        keyboardIdentifier, imeInfo);
-                boolean noLayoutFound = result.getLayoutDescriptor() == null;
-                if (!noLayoutFound) {
-                    selectedLayouts.add(result.getLayoutDescriptor());
+
+                if (shouldLogConfiguration) {
+                    logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
+                            !mDataStore.hasInputDeviceEntry(key));
                 }
-                resultList.add(result);
-                hasMissingLayout |= noLayoutFound;
-            }
-
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "Layouts selected for input device: " + keyboardIdentifier
-                                + " -> selectedLayouts: " + selectedLayouts);
-            }
-
-            // If even one layout not configured properly, we need to ask user to configure
-            // the keyboard properly from the Settings.
-            if (hasMissingLayout) {
-                selectedLayouts.clear();
-            }
-
-            config.setConfiguredLayouts(selectedLayouts);
-
-            synchronized (mDataStore) {
-                try {
-                    final String key = keyboardIdentifier.toString();
-                    if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
-                        // Need to show the notification only if layout selection changed
-                        // from the previous configuration
-                        needToShowNotification = true;
-                    }
-
-                    if (shouldLogConfiguration) {
-                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
-                                !mDataStore.hasInputDeviceEntry(key));
-                    }
-                } finally {
-                    mDataStore.saveIfNeeded();
-                }
+            } finally {
+                mDataStore.saveIfNeeded();
             }
         }
         if (needToShowNotification) {
@@ -275,63 +255,6 @@
         }
     }
 
-    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
-        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
-        // If our locale doesn't have a language for some reason, then we don't really have a
-        // reasonable default.
-        if (TextUtils.isEmpty(systemLocale.getLanguage())) {
-            return null;
-        }
-        final List<KeyboardLayout> layouts = new ArrayList<>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
-            // Only select a default when we know the layout is appropriate. For now, this
-            // means it's a custom layout for a specific keyboard.
-            if (layout.getVendorId() != inputDevice.getVendorId()
-                    || layout.getProductId() != inputDevice.getProductId()) {
-                return;
-            }
-            final LocaleList locales = layout.getLocales();
-            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale != null && isCompatibleLocale(systemLocale, locale)) {
-                    layouts.add(layout);
-                    break;
-                }
-            }
-        });
-
-        if (layouts.isEmpty()) {
-            return null;
-        }
-
-        // First sort so that ones with higher priority are listed at the top
-        Collections.sort(layouts);
-        // Next we want to try to find an exact match of language, country and variant.
-        for (KeyboardLayout layout : layouts) {
-            final LocaleList locales = layout.getLocales();
-            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale != null && locale.getCountry().equals(systemLocale.getCountry())
-                        && locale.getVariant().equals(systemLocale.getVariant())) {
-                    return layout.getDescriptor();
-                }
-            }
-        }
-        // Then try an exact match of language and country
-        for (KeyboardLayout layout : layouts) {
-            final LocaleList locales = layout.getLocales();
-            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) {
-                    return layout.getDescriptor();
-                }
-            }
-        }
-
-        // Give up and just use the highest priority layout with matching language
-        return layouts.get(0).getDescriptor();
-    }
-
     private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
         // Different languages are never compatible
         if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
@@ -343,11 +266,19 @@
                 || systemLocale.getCountry().equals(keyboardLocale.getCountry());
     }
 
+    @MainThread
     private void updateKeyboardLayouts() {
         // Scan all input devices state for keyboard layouts that have been uninstalled.
-        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+        final HashSet<String> availableKeyboardLayouts = new HashSet<>();
         visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
                 availableKeyboardLayouts.add(layout.getDescriptor()));
+
+        // If available layouts don't change, there is no need to reload layouts.
+        if (mAvailableLayouts.equals(availableKeyboardLayouts)) {
+            return;
+        }
+        mAvailableLayouts = availableKeyboardLayouts;
+
         synchronized (mDataStore) {
             try {
                 mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
@@ -374,53 +305,6 @@
     }
 
     @AnyThread
-    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
-            final InputDeviceIdentifier identifier) {
-        if (useNewSettingsUi()) {
-            // Provide all supported keyboard layouts since Ime info is not provided
-            return getKeyboardLayouts();
-        }
-        final String[] enabledLayoutDescriptors =
-                getEnabledKeyboardLayoutsForInputDevice(identifier);
-        final ArrayList<KeyboardLayout> enabledLayouts =
-                new ArrayList<>(enabledLayoutDescriptors.length);
-        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
-        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
-            boolean mHasSeenDeviceSpecificLayout;
-
-            @Override
-            public void visitKeyboardLayout(Resources resources,
-                    int keyboardLayoutResId, KeyboardLayout layout) {
-                // First check if it's enabled. If the keyboard layout is enabled then we always
-                // want to return it as a possible layout for the device.
-                for (String s : enabledLayoutDescriptors) {
-                    if (s != null && s.equals(layout.getDescriptor())) {
-                        enabledLayouts.add(layout);
-                        return;
-                    }
-                }
-                // Next find any potential layouts that aren't yet enabled for the device. For
-                // devices that have special layouts we assume there's a reason that the generic
-                // layouts don't work for them so we don't want to return them since it's likely
-                // to result in a poor user experience.
-                if (layout.getVendorId() == identifier.getVendorId()
-                        && layout.getProductId() == identifier.getProductId()) {
-                    if (!mHasSeenDeviceSpecificLayout) {
-                        mHasSeenDeviceSpecificLayout = true;
-                        potentialLayouts.clear();
-                    }
-                    potentialLayouts.add(layout);
-                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
-                        && !mHasSeenDeviceSpecificLayout) {
-                    potentialLayouts.add(layout);
-                }
-            }
-        });
-        return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray(
-                KeyboardLayout[]::new);
-    }
-
-    @AnyThread
     @Nullable
     public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -580,195 +464,16 @@
         return LocaleList.forLanguageTags(languageTags.replace('|', ','));
     }
 
-    @AnyThread
-    @Nullable
-    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
-        if (useNewSettingsUi()) {
-            Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported");
-            return null;
-        }
-        String key = new KeyboardIdentifier(identifier).toString();
-        synchronized (mDataStore) {
-            String layout;
-            // try loading it using the layout descriptor if we have it
-            layout = mDataStore.getCurrentKeyboardLayout(key);
-            if (layout == null && !key.equals(identifier.getDescriptor())) {
-                // if it doesn't exist fall back to the device descriptor
-                layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
-                        + identifier.toString() + ": " + layout);
-            }
-            return layout;
-        }
-    }
-
-    @AnyThread
-    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
-        if (useNewSettingsUi()) {
-            Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported");
-            return;
-        }
-
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-        String key = new KeyboardIdentifier(identifier).toString();
-        synchronized (mDataStore) {
-            try {
-                if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
-                                + " key: " + key
-                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
-                    }
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
-    }
-
-    @AnyThread
-    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
-        if (useNewSettingsUi()) {
-            Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
-            return new String[0];
-        }
-        String key = new KeyboardIdentifier(identifier).toString();
-        synchronized (mDataStore) {
-            String[] layouts = mDataStore.getKeyboardLayouts(key);
-            if ((layouts == null || layouts.length == 0)
-                    && !key.equals(identifier.getDescriptor())) {
-                layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
-            }
-            return layouts;
-        }
-    }
-
-    @AnyThread
-    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
-        if (useNewSettingsUi()) {
-            Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported");
-            return;
-        }
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = new KeyboardIdentifier(identifier).toString();
-        synchronized (mDataStore) {
-            try {
-                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
-                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
-                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-                }
-                if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
-                        && !Objects.equals(oldLayout,
-                        mDataStore.getCurrentKeyboardLayout(key))) {
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
-    }
-
-    @AnyThread
-    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
-        if (useNewSettingsUi()) {
-            Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported");
-            return;
-        }
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = new KeyboardIdentifier(identifier).toString();
-        synchronized (mDataStore) {
-            try {
-                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
-                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
-                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-                }
-                boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
-                if (!key.equals(identifier.getDescriptor())) {
-                    // We need to remove from both places to ensure it is gone
-                    removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
-                            keyboardLayoutDescriptor);
-                }
-                if (removed && !Objects.equals(oldLayout,
-                        mDataStore.getCurrentKeyboardLayout(key))) {
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
-    }
-
-    @AnyThread
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        if (useNewSettingsUi()) {
-            Slog.e(TAG, "switchKeyboardLayout API not supported");
-            return;
-        }
-        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
-    }
-
-    @MainThread
-    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
-        final InputDevice device = getInputDevice(deviceId);
-        if (device != null) {
-            final boolean changed;
-            final String keyboardLayoutDescriptor;
-
-            String key = new KeyboardIdentifier(device.getIdentifier()).toString();
-            synchronized (mDataStore) {
-                try {
-                    changed = mDataStore.switchKeyboardLayout(key, direction);
-                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
-                            key);
-                } finally {
-                    mDataStore.saveIfNeeded();
-                }
-            }
-
-            if (changed) {
-                if (mSwitchedKeyboardLayoutToast != null) {
-                    mSwitchedKeyboardLayoutToast.cancel();
-                    mSwitchedKeyboardLayoutToast = null;
-                }
-                if (keyboardLayoutDescriptor != null) {
-                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
-                    if (keyboardLayout != null) {
-                        mSwitchedKeyboardLayoutToast = Toast.makeText(
-                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
-                        mSwitchedKeyboardLayoutToast.show();
-                    }
-                }
-
-                reloadKeyboardLayouts();
-            }
-        }
-    }
-
     @Nullable
     @AnyThread
     public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag,
             String layoutType) {
         String keyboardLayoutDescriptor;
-        if (useNewSettingsUi()) {
-            synchronized (mImeInfoLock) {
-                KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
-                        new KeyboardIdentifier(identifier, languageTag, layoutType),
-                        mCurrentImeInfo);
-                keyboardLayoutDescriptor = result.getLayoutDescriptor();
-            }
-        } else {
-            keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        synchronized (mImeInfoLock) {
+            KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+                    new KeyboardIdentifier(identifier, languageTag, layoutType),
+                    mCurrentImeInfo);
+            keyboardLayoutDescriptor = result.getLayoutDescriptor();
         }
         if (keyboardLayoutDescriptor == null) {
             return null;
@@ -797,10 +502,6 @@
     public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
             InputDeviceIdentifier identifier, @UserIdInt int userId,
             @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
-        if (!useNewSettingsUi()) {
-            Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
-            return FAILED;
-        }
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return FAILED;
@@ -820,10 +521,6 @@
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
             @Nullable InputMethodSubtype imeSubtype,
             String keyboardLayoutDescriptor) {
-        if (!useNewSettingsUi()) {
-            Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported");
-            return;
-        }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
         InputDevice inputDevice = getInputDevice(identifier);
@@ -854,10 +551,6 @@
     public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
             @Nullable InputMethodSubtype imeSubtype) {
-        if (!useNewSettingsUi()) {
-            Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported");
-            return new KeyboardLayout[0];
-        }
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return new KeyboardLayout[0];
@@ -923,10 +616,6 @@
     public void onInputMethodSubtypeChanged(@UserIdInt int userId,
             @Nullable InputMethodSubtypeHandle subtypeHandle,
             @Nullable InputMethodSubtype subtype) {
-        if (!useNewSettingsUi()) {
-            Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported");
-            return;
-        }
         if (subtypeHandle == null) {
             if (DEBUG) {
                 Slog.d(TAG, "No InputMethod is running, ignoring change");
@@ -1289,9 +978,6 @@
                     onInputDeviceAdded(deviceId);
                 }
                 return true;
-            case MSG_SWITCH_KEYBOARD_LAYOUT:
-                handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
-                return true;
             case MSG_RELOAD_KEYBOARD_LAYOUTS:
                 reloadKeyboardLayouts();
                 return true;
@@ -1303,10 +989,6 @@
         }
     }
 
-    private boolean useNewSettingsUi() {
-        return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
-    }
-
     @Nullable
     private InputDevice getInputDevice(int deviceId) {
         InputManager inputManager = mContext.getSystemService(InputManager.class);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 31083fd..7859253 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,7 +27,6 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -42,7 +41,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -74,7 +72,7 @@
             new HashMap<String, InputDeviceState>();
 
     // The interface for methods which should be replaced by the test harness.
-    private Injector mInjector;
+    private final Injector mInjector;
 
     // True if the data has been loaded.
     private boolean mLoaded;
@@ -83,7 +81,7 @@
     private boolean mDirty;
 
     // Storing key remapping
-    private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+    private final Map<Integer, Integer> mKeyRemapping = new HashMap<>();
 
     public PersistentDataStore() {
         this(new Injector());
@@ -130,22 +128,6 @@
     }
 
     @Nullable
-    public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
-        return state != null ? state.getCurrentKeyboardLayout() : null;
-    }
-
-    public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
-            String keyboardLayoutDescriptor) {
-        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
-        if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
-            setDirty();
-            return true;
-        }
-        return false;
-    }
-
-    @Nullable
     public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         return state != null ? state.getKeyboardLayout(key) : null;
@@ -171,43 +153,6 @@
         return false;
     }
 
-    public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
-        if (state == null) {
-            return (String[])ArrayUtils.emptyArray(String.class);
-        }
-        return state.getKeyboardLayouts();
-    }
-
-    public boolean addKeyboardLayout(String inputDeviceDescriptor,
-            String keyboardLayoutDescriptor) {
-        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
-        if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
-            setDirty();
-            return true;
-        }
-        return false;
-    }
-
-    public boolean removeKeyboardLayout(String inputDeviceDescriptor,
-            String keyboardLayoutDescriptor) {
-        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
-        if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
-            setDirty();
-            return true;
-        }
-        return false;
-    }
-
-    public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
-        if (state != null && state.switchKeyboardLayout(direction)) {
-            setDirty();
-            return true;
-        }
-        return false;
-    }
-
     public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId,
             int brightness) {
         InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
@@ -417,9 +362,6 @@
                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
 
         private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
-        @Nullable
-        private String mCurrentKeyboardLayout;
-        private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
         private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
 
         private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
@@ -465,49 +407,6 @@
             return true;
         }
 
-        @Nullable
-        public String getCurrentKeyboardLayout() {
-            return mCurrentKeyboardLayout;
-        }
-
-        public boolean setCurrentKeyboardLayout(String keyboardLayout) {
-            if (Objects.equals(mCurrentKeyboardLayout, keyboardLayout)) {
-                return false;
-            }
-            addKeyboardLayout(keyboardLayout);
-            mCurrentKeyboardLayout = keyboardLayout;
-            return true;
-        }
-
-        public String[] getKeyboardLayouts() {
-            if (mKeyboardLayouts.isEmpty()) {
-                return (String[])ArrayUtils.emptyArray(String.class);
-            }
-            return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
-        }
-
-        public boolean addKeyboardLayout(String keyboardLayout) {
-            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
-            if (index >= 0) {
-                return false;
-            }
-            mKeyboardLayouts.add(-index - 1, keyboardLayout);
-            if (mCurrentKeyboardLayout == null) {
-                mCurrentKeyboardLayout = keyboardLayout;
-            }
-            return true;
-        }
-
-        public boolean removeKeyboardLayout(String keyboardLayout) {
-            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
-            if (index < 0) {
-                return false;
-            }
-            mKeyboardLayouts.remove(index);
-            updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
-            return true;
-        }
-
         public boolean setKeyboardBacklightBrightness(int lightId, int brightness) {
             if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) {
                 return false;
@@ -521,48 +420,8 @@
             return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness);
         }
 
-        private void updateCurrentKeyboardLayoutIfRemoved(
-                String removedKeyboardLayout, int removedIndex) {
-            if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) {
-                if (!mKeyboardLayouts.isEmpty()) {
-                    int index = removedIndex;
-                    if (index == mKeyboardLayouts.size()) {
-                        index = 0;
-                    }
-                    mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
-                } else {
-                    mCurrentKeyboardLayout = null;
-                }
-            }
-        }
-
-        public boolean switchKeyboardLayout(int direction) {
-            final int size = mKeyboardLayouts.size();
-            if (size < 2) {
-                return false;
-            }
-            int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
-            assert index >= 0;
-            if (direction > 0) {
-                index = (index + 1) % size;
-            } else {
-                index = (index + size - 1) % size;
-            }
-            mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
-            return true;
-        }
-
         public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
             boolean changed = false;
-            for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
-                String keyboardLayout = mKeyboardLayouts.get(i);
-                if (!availableKeyboardLayouts.contains(keyboardLayout)) {
-                    Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
-                    mKeyboardLayouts.remove(i);
-                    updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
-                    changed = true;
-                }
-            }
             List<String> removedEntries = new ArrayList<>();
             for (String key : mKeyboardLayoutMap.keySet()) {
                 if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
@@ -582,27 +441,7 @@
                 throws IOException, XmlPullParserException {
             final int outerDepth = parser.getDepth();
             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-                if (parser.getName().equals("keyboard-layout")) {
-                    String descriptor = parser.getAttributeValue(null, "descriptor");
-                    if (descriptor == null) {
-                        throw new XmlPullParserException(
-                                "Missing descriptor attribute on keyboard-layout.");
-                    }
-                    String current = parser.getAttributeValue(null, "current");
-                    if (mKeyboardLayouts.contains(descriptor)) {
-                        throw new XmlPullParserException(
-                                "Found duplicate keyboard layout.");
-                    }
-
-                    mKeyboardLayouts.add(descriptor);
-                    if (current != null && current.equals("true")) {
-                        if (mCurrentKeyboardLayout != null) {
-                            throw new XmlPullParserException(
-                                    "Found multiple current keyboard layouts.");
-                        }
-                        mCurrentKeyboardLayout = descriptor;
-                    }
-                } else if (parser.getName().equals("keyed-keyboard-layout")) {
+                if (parser.getName().equals("keyed-keyboard-layout")) {
                     String key = parser.getAttributeValue(null, "key");
                     if (key == null) {
                         throw new XmlPullParserException(
@@ -676,27 +515,9 @@
                     }
                 }
             }
-
-            // Maintain invariant that layouts are sorted.
-            Collections.sort(mKeyboardLayouts);
-
-            // Maintain invariant that there is always a current keyboard layout unless
-            // there are none installed.
-            if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
-                mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
-            }
         }
 
         public void saveToXml(TypedXmlSerializer serializer) throws IOException {
-            for (String layout : mKeyboardLayouts) {
-                serializer.startTag(null, "keyboard-layout");
-                serializer.attribute(null, "descriptor", layout);
-                if (layout.equals(mCurrentKeyboardLayout)) {
-                    serializer.attributeBoolean(null, "current", true);
-                }
-                serializer.endTag(null, "keyboard-layout");
-            }
-
             for (String key : mKeyboardLayoutMap.keySet()) {
                 serializer.startTag(null, "keyed-keyboard-layout");
                 serializer.attribute(null, "key", key);
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 31ce630..ffc2319 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -196,7 +196,10 @@
         return true;
     }
 
-    private void sendResultReceiverFailure(ResultReceiver resultReceiver) {
+    private void sendResultReceiverFailure(@Nullable ResultReceiver resultReceiver) {
+        if (resultReceiver == null) {
+            return;
+        }
         resultReceiver.send(
                 mIsInputShown.getAsBoolean()
                         ? InputMethodManager.RESULT_UNCHANGED_SHOWN
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c80f988..19562ef 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -102,12 +102,14 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.ICeStorageLockEventListener;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.security.AndroidKeyStoreMaintenance;
-import android.security.Authorization;
+import android.security.KeyStoreAuthorization;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore.recovery.KeyChainProtectionParams;
@@ -293,6 +295,7 @@
     private final SyntheticPasswordManager mSpManager;
 
     private final KeyStore mKeyStore;
+    private final KeyStoreAuthorization mKeyStoreAuthorization;
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
     private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
 
@@ -337,6 +340,8 @@
     private final CopyOnWriteArrayList<LockSettingsStateListener> mLockSettingsStateListeners =
             new CopyOnWriteArrayList<>();
 
+    private final StorageManagerInternal mStorageManagerInternal;
+
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
     // the user unlocks the account and credential-encrypted storage is available.
@@ -576,6 +581,10 @@
             return null;
         }
 
+        public StorageManagerInternal getStorageManagerInternal() {
+            return LocalServices.getService(StorageManagerInternal.class);
+        }
+
         public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
             return new SyntheticPasswordManager(getContext(), storage, getUserManager(),
                     new PasswordSlotManager());
@@ -627,6 +636,10 @@
             }
         }
 
+        public KeyStoreAuthorization getKeyStoreAuthorization() {
+            return KeyStoreAuthorization.getInstance();
+        }
+
         public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
             return new UnifiedProfilePasswordCache(ks);
         }
@@ -650,6 +663,7 @@
         mInjector = injector;
         mContext = injector.getContext();
         mKeyStore = injector.getKeyStore();
+        mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
         mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
         mHandler = injector.getHandler(injector.getServiceThread());
         mStrongAuth = injector.getStrongAuth();
@@ -666,6 +680,7 @@
         mNotificationManager = injector.getNotificationManager();
         mUserManager = injector.getUserManager();
         mStorageManager = injector.getStorageManager();
+        mStorageManagerInternal = injector.getStorageManagerInternal();
         mStrongAuthTracker = injector.getStrongAuthTracker();
         mStrongAuthTracker.register(mStrongAuth);
         mGatekeeperPasswords = new LongSparseArray<>();
@@ -919,8 +934,34 @@
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
         mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
                 mInjector.getFaceManager(), mInjector.getBiometricManager());
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            mStorageManagerInternal.registerStorageLockEventListener(mCeStorageLockEventListener);
+        }
     }
 
+    private final ICeStorageLockEventListener mCeStorageLockEventListener =
+            new ICeStorageLockEventListener() {
+                @Override
+                public void onStorageLocked(int userId) {
+                    Slog.i(TAG, "Storage lock event received for " + userId);
+                    if (android.os.Flags.allowPrivateProfile()
+                            && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                            && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                        mHandler.post(() -> {
+                            UserProperties userProperties =
+                                    mUserManager.getUserProperties(UserHandle.of(userId));
+                            if (userProperties != null
+                                    && userProperties.getAllowStoppingUserWithDelayedLocking()) {
+                                int strongAuthRequired = LockPatternUtils.StrongAuthTracker
+                                        .getDefaultFlags(mContext);
+                                requireStrongAuth(strongAuthRequired, userId);
+                            }
+                        });
+                    }
+                }};
+
     private void loadEscrowData() {
         mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
     }
@@ -1460,7 +1501,7 @@
     }
 
     private void unlockKeystore(int userId, SyntheticPassword sp) {
-        Authorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
+        mKeyStoreAuthorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING
index ddf3d76..256d9ba 100644
--- a/services/core/java/com/android/server/locksettings/TEST_MAPPING
+++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING
@@ -24,5 +24,11 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            // TODO(b/332974906): Promote in presubmit-large.
+            "name": "CtsDevicePolicyManagerTestCases_LockSettings_NoFlakes"
+        }
     ]
 }
diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
index 66cafab..e4f2ec3 100644
--- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java
+++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
@@ -44,7 +44,6 @@
  * Note: When instantiating this class, {@link MediaSessionService} will only use the constructor
  * without any parameters.
  */
-// TODO: Move this class to apex/media/
 public abstract class MediaKeyDispatcher {
     @IntDef(flag = true, value = {
             KEY_EVENT_SINGLE_TAP,
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index eb4e6e4..62a9471 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -849,7 +849,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -876,7 +876,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -911,7 +911,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -938,7 +938,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -965,7 +965,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -992,7 +992,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -1017,7 +1017,7 @@
             }
         }
         if (deadCallbackHolders != null) {
-            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+            removeControllerHoldersSafely(deadCallbackHolders);
         }
     }
 
@@ -1042,7 +1042,7 @@
             }
         }
         // After notifying clear all listeners
-        mControllerCallbackHolders.clear();
+        removeControllerHoldersSafely(null);
     }
 
     private PlaybackState getStateWithUpdatedPosition() {
@@ -1090,6 +1090,17 @@
         return -1;
     }
 
+    private void removeControllerHoldersSafely(
+            Collection<ISessionControllerCallbackHolder> holders) {
+        synchronized (mLock) {
+            if (holders == null) {
+                mControllerCallbackHolders.clear();
+            } else {
+                mControllerCallbackHolders.removeAll(holders);
+            }
+        }
+    }
+
     private PlaybackInfo getVolumeAttributes() {
         int volumeType;
         AudioAttributes attributes;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index e1e2b3e..3949dfe 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1617,7 +1617,8 @@
         intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
 
         final ActivityOptions activityOptions = ActivityOptions.makeBasic();
-        activityOptions.setIgnorePendingIntentCreatorForegroundState(true);
+        activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
         final PendingIntent pendingIntent = PendingIntent.getActivity(
                 mContext, 0, new Intent(mConfig.settingsAction), PendingIntent.FLAG_IMMUTABLE,
                 activityOptions.toBundle());
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7b12d86..68e0eaa 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -27,6 +27,10 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -44,7 +48,13 @@
     private final Context mContext;
     private final RankingHandler mRankingHandler;
 
-
+    @UsesReflection(
+            value = {
+                @KeepTarget(
+                        kind = KeepItemKind.CLASS_AND_MEMBERS,
+                        instanceOfClassConstantExclusive = NotificationSignalExtractor.class,
+                        methodName = "<init>")
+            })
     public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
             ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
         mContext = context;
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4eb8b2b..c8fd7e4 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -184,6 +184,16 @@
                         throwInvalidBugreportFileForCallerException(
                                 bugreportFile, callingInfo.second);
                     }
+
+                    boolean keepBugreportOnRetrieval = false;
+                    if (onboardingBugreportV2Enabled()) {
+                        keepBugreportOnRetrieval = mBugreportFilesToPersist.contains(
+                                bugreportFile);
+                    }
+
+                    if (!keepBugreportOnRetrieval) {
+                        bugreportFilesForUid.remove(bugreportFile);
+                    }
                 } else {
                     ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
                     if (bugreportFilesForCaller != null
diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING
index d937af1..50c8964 100644
--- a/services/core/java/com/android/server/os/TEST_MAPPING
+++ b/services/core/java/com/android/server/os/TEST_MAPPING
@@ -45,6 +45,10 @@
     },
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
+      "name": "CtsRootBugreportTestCases"
+    },
+    {
+      "file_patterns": ["Bugreport[^/]*\\.java"],
       "name": "ShellTests"
     }
   ]
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index b87256d..712413c 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -60,9 +60,12 @@
     public static boolean isIntentRedirectionAllowed(Context context,
             AppCloningDeviceConfigHelper appCloningDeviceConfigHelper, boolean resolveForStart,
             long flags) {
+        boolean canMatchCloneProfile = (flags & PackageManager.MATCH_CLONE_PROFILE) != 0
+                || (flags & PackageManager.MATCH_CLONE_PROFILE_LONG) != 0;
         return isAppCloningBuildingBlocksEnabled(context, appCloningDeviceConfigHelper)
-                    && (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0)
-                    && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
+                && (resolveForStart
+                    || (canMatchCloneProfile
+                        && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
     }
 
     public NoFilteringResolver(ComponentResolverApi componentResolver,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index e2f4d18..fda4dc6 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -187,7 +187,7 @@
     }
 
     public static boolean isArchivingEnabled() {
-        return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+        return Flags.archiving();
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 29320ae..a5cd821 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -838,7 +838,8 @@
 
         if ((params.installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0
                 && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
-                && !Build.IS_DEBUGGABLE) {
+                && !Build.IS_DEBUGGABLE
+                && !PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
             // If the bypass flag is set, but not running as system root or shell then remove
             // the flag
             params.installFlags &= ~PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3eeeae7..80a5f3a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1085,11 +1085,17 @@
         final boolean isUpdateOwnershipEnforcementEnabled =
                 mPm.isUpdateOwnershipEnforcementAvailable()
                         && existingUpdateOwnerPackageName != null;
+        // For an installation that un-archives an app, if the installer doesn't have the
+        // INSTALL_PACKAGES permission, the user should have already been prompted to confirm the
+        // un-archive request. There's no need for another confirmation during the installation.
+        final boolean isInstallUnarchive =
+                (params.installFlags & PackageManager.INSTALL_UNARCHIVE) != 0;
 
         // Device owners and affiliated profile owners are allowed to silently install packages, so
         // the permission check is waived if the installer is the device owner.
         final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
-                || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
+                || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall
+                || isInstallUnarchive;
 
         if (noUserActionNecessary) {
             return userActionNotTypicallyNeededResponse;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ff41245..e3261bd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1882,11 +1882,10 @@
                 && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Allow delayed locking since some profile types want to be able to unlock again via
             // biometrics.
-            ActivityManager.getService()
-                    .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+            ActivityManager.getService().stopUserWithDelayedLocking(userId, null);
             return;
         }
-        ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+        ActivityManager.getService().stopUserWithCallback(userId, null);
     }
 
     private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
@@ -6132,7 +6131,7 @@
             if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
             int res;
             try {
-                res = ActivityManager.getService().stopUser(userId, /* force= */ true,
+                res = ActivityManager.getService().stopUserWithCallback(userId,
                 new IStopUserCallback.Stub() {
                             @Override
                             public void userStopped(int userIdParam) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 324637c..4e02470 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -721,7 +721,8 @@
                         if (!am.isProfileForeground(UserHandle.of(userId))
                                 && userId != UserHandle.USER_SYSTEM) {
                             try {
-                                ActivityManager.getService().stopUser(userId, false, null);
+                                ActivityManager.getService().stopUserExceptCertainProfiles(
+                                        userId, false, null);
                             } catch (RemoteException e) {
                                 throw e.rethrowAsRuntimeException();
                             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7db83d7..b4919a4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -174,7 +174,6 @@
 import android.service.vr.IPersistentVrStateCallbacks;
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableBoolean;
@@ -4121,14 +4120,10 @@
 
     private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
             IBinder focusedToken) {
-        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
-            IBinder targetWindowToken =
-                    mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
-            InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
-                    event.getDisplayId(), targetWindowToken);
-        } else {
-            mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
-        }
+        IBinder targetWindowToken =
+                mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+        InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+                event.getDisplayId(), targetWindowToken);
     }
 
     private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 2623025..9ca4e27 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -251,12 +251,6 @@
          */
         public int getCameraLensCoverState();
 
-        /**
-         * Switch the keyboard layout for the given device.
-         * Direction should be +1 or -1 to go to the next or previous keyboard layout.
-         */
-        public void switchKeyboardLayout(int deviceId, int direction);
-
         public void shutdown(boolean confirm);
         public void reboot(boolean confirm);
         public void rebootSafeMode(boolean confirm);
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 9f0a975..8f99d28 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -568,23 +568,25 @@
             int index = 0;
 
             Channel[] channels = getEnergyMeterInfo();
-            for (Channel channel : channels) {
-                PowerMonitor monitor = new PowerMonitor(index++,
-                        PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT,
-                        getChannelName(channel));
-                monitors.add(monitor);
-                states.add(new PowerMonitorState(monitor, channel.id));
+            if (channels != null) {
+                for (Channel channel : channels) {
+                    PowerMonitor monitor = new PowerMonitor(index++,
+                            PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT,
+                            getChannelName(channel));
+                    monitors.add(monitor);
+                    states.add(new PowerMonitorState(monitor, channel.id));
+                }
             }
-
             EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
-            for (EnergyConsumer consumer : energyConsumers) {
-                PowerMonitor monitor = new PowerMonitor(index++,
-                        PowerMonitor.POWER_MONITOR_TYPE_CONSUMER,
-                        getEnergyConsumerName(consumer, energyConsumers));
-                monitors.add(monitor);
-                states.add(new PowerMonitorState(monitor, consumer.id));
+            if (energyConsumers != null) {
+                for (EnergyConsumer consumer : energyConsumers) {
+                    PowerMonitor monitor = new PowerMonitor(index++,
+                            PowerMonitor.POWER_MONITOR_TYPE_CONSUMER,
+                            getEnergyConsumerName(consumer, energyConsumers));
+                    monitors.add(monitor);
+                    states.add(new PowerMonitorState(monitor, consumer.id));
+                }
             }
-
             mPowerMonitors = monitors.toArray(new PowerMonitor[monitors.size()]);
             mPowerMonitorStates = states.toArray(new PowerMonitorState[monitors.size()]);
         }
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 3c0547e..c24240b 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -30,6 +30,7 @@
 import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_PROVIDER;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.apex.CompressedApexInfo;
 import android.apex.CompressedApexInfoList;
 import android.content.Context;
@@ -37,6 +38,7 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.hardware.boot.IBootControl;
+import android.hardware.security.secretkeeper.ISecretkeeper;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.os.Binder;
@@ -52,6 +54,7 @@
 import android.os.ShellCallback;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
+import android.security.AndroidKeyStoreMaintenance;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.FastImmutableArraySet;
@@ -68,6 +71,7 @@
 import com.android.server.Watchdog;
 import com.android.server.pm.ApexManager;
 import com.android.server.recoverysystem.hal.BootControlHIDL;
+import com.android.server.utils.Slogf;
 
 import libcore.io.IoUtils;
 
@@ -122,6 +126,8 @@
     static final String LSKF_CAPTURED_TIMESTAMP_PREF = "lskf_captured_timestamp";
     static final String LSKF_CAPTURED_COUNT_PREF = "lskf_captured_count";
 
+    static final String RECOVERY_WIPE_DATA_COMMAND = "--wipe_data";
+
     private final Injector mInjector;
     private final Context mContext;
 
@@ -525,18 +531,57 @@
     @Override // Binder call
     public void rebootRecoveryWithCommand(String command) {
         if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
+
+        boolean isForcedWipe = command != null && command.contains(RECOVERY_WIPE_DATA_COMMAND);
         synchronized (sRequestLock) {
             if (!setupOrClearBcb(true, command)) {
                 Slog.e(TAG, "rebootRecoveryWithCommand failed to setup BCB");
                 return;
             }
 
+            if (isForcedWipe) {
+                deleteSecrets();
+                // TODO: consider adding a dedicated forced-wipe-reboot method to PowerManager and
+                // calling here.
+            }
+
             // Having set up the BCB, go ahead and reboot.
             PowerManager pm = mInjector.getPowerManager();
             pm.reboot(PowerManager.REBOOT_RECOVERY);
         }
     }
 
+    private static void deleteSecrets() {
+        Slogf.w(TAG, "deleteSecrets");
+        try {
+            AndroidKeyStoreMaintenance.deleteAllKeys();
+        } catch (android.security.KeyStoreException e) {
+            Log.wtf(TAG, "Failed to delete all keys from keystore.", e);
+        }
+
+        try {
+            ISecretkeeper secretKeeper = getSecretKeeper();
+            if (secretKeeper != null) {
+                Slogf.i(TAG, "ISecretkeeper.deleteAll();");
+                secretKeeper.deleteAll();
+            }
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failed to delete all secrets from secretkeeper.", e);
+        }
+    }
+
+    private static @Nullable ISecretkeeper getSecretKeeper() {
+        ISecretkeeper result = null;
+        try {
+            result = ISecretkeeper.Stub.asInterface(
+                ServiceManager.waitForDeclaredService(ISecretkeeper.DESCRIPTOR + "/default"));
+        } catch (SecurityException e) {
+            Slog.w(TAG, "Does not have permissions to get AIDL secretkeeper service");
+        }
+
+        return result;
+    }
+
     private void enforcePermissionForResumeOnReboot() {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY)
                 != PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 2b05993..f62c76e 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -61,7 +61,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.security.Authorization;
+import android.security.KeyStoreAuthorization;
 import android.service.trust.GrantTrustResult;
 import android.service.trust.TrustAgentService;
 import android.text.TextUtils;
@@ -155,6 +155,7 @@
     /* package */ final TrustArchive mArchive = new TrustArchive();
     private final Context mContext;
     private final LockPatternUtils mLockPatternUtils;
+    private final KeyStoreAuthorization mKeyStoreAuthorization;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
     private FingerprintManager mFingerprintManager;
@@ -252,25 +253,27 @@
      * cases.
      */
     protected static class Injector {
-        private final LockPatternUtils mLockPatternUtils;
-        private final Looper mLooper;
+        private final Context mContext;
 
-        public Injector(LockPatternUtils lockPatternUtils, Looper looper) {
-            mLockPatternUtils = lockPatternUtils;
-            mLooper = looper;
+        public Injector(Context context) {
+            mContext = context;
         }
 
         LockPatternUtils getLockPatternUtils() {
-            return mLockPatternUtils;
+            return new LockPatternUtils(mContext);
+        }
+
+        KeyStoreAuthorization getKeyStoreAuthorization() {
+            return KeyStoreAuthorization.getInstance();
         }
 
         Looper getLooper() {
-            return mLooper;
+            return Looper.myLooper();
         }
     }
 
     public TrustManagerService(Context context) {
-        this(context, new Injector(new LockPatternUtils(context), Looper.myLooper()));
+        this(context, new Injector(context));
     }
 
     protected TrustManagerService(Context context, Injector injector) {
@@ -280,6 +283,7 @@
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mLockPatternUtils = injector.getLockPatternUtils();
+        mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
         mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
     }
@@ -915,16 +919,16 @@
                 int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
                         ? resolveProfileParent(userId) : userId;
 
-                Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+                mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(authUserId),
                         isWeakUnlockMethodEnabled(authUserId));
             } else {
-                Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+                mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(userId), false);
             }
         } else {
             // Notify Keystore that the device is now unlocked for the user.  Note that for unlocks
             // with LSKF, this is redundant with the call from LockSettingsService which provides
             // the password.  However, for unlocks with biometric or trust agent, this is required.
-            Authorization.onDeviceUnlocked(userId, /* password= */ null);
+            mKeyStoreAuthorization.onDeviceUnlocked(userId, /* password= */ null);
         }
     }
 
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 6fc9d9a..ad54efc 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -32,8 +32,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -42,10 +43,11 @@
  * The base class for all vibrations.
  */
 abstract class Vibration {
-    private static final SimpleDateFormat DEBUG_TIME_FORMAT =
-            new SimpleDateFormat("HH:mm:ss.SSS");
-    private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
-            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+    private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+            "HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+    private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+            "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
     // Used to generate globally unique vibration ids.
     private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
 
@@ -240,10 +242,12 @@
 
         @Override
         public String toString() {
-            return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))
-                    + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))
-                    + ", endTime: "
-                    + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))
+            return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format(
+                    Instant.ofEpochMilli(mCreateTime))
+                    + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format(
+                    Instant.ofEpochMilli(mStartTime))
+                    + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format(
+                    Instant.ofEpochMilli(mEndTime)))
                     + ", durationMs: " + mDurationMs
                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
                     + ", playedEffect: " + mPlayedEffect
@@ -267,12 +271,14 @@
             boolean isExternalVibration = mPlayedEffect == null;
             String timingsStr = String.format(Locale.ROOT,
                     "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
-                    DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+                    DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
                     isExternalVibration ? "external" : "effect",
                     mStatus.name().toLowerCase(Locale.ROOT),
                     mDurationMs,
-                    mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
-                    mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
+                    mStartTime == 0 ? ""
+                            : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)),
+                    mEndTime == 0 ? ""
+                            : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)));
             String paramStr = String.format(Locale.ROOT,
                     " | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
                     VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
@@ -307,10 +313,12 @@
             pw.increaseIndent();
             pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
             pw.println("durationMs = " + mDurationMs);
-            pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)));
-            pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)));
-            pw.println("endTime = "
-                    + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
+            pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format(
+                    Instant.ofEpochMilli(mCreateTime)));
+            pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format(
+                    Instant.ofEpochMilli(mStartTime)));
+            pw.println("endTime = " + (mEndTime == 0 ? null
+                    : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))));
             pw.println("playedEffect = " + mPlayedEffect);
             pw.println("originalEffect = " + mOriginalEffect);
             pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 10317c9..b33fa6f 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -51,8 +51,9 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
@@ -66,8 +67,8 @@
     private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
     private static final int NO_SCALE = -1;
 
-    private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
-            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+    private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+            "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
 
     private final VibrationParamsRecords mVibrationParamsRecords;
     private final VibratorControllerHolder mVibratorControllerHolder;
@@ -567,7 +568,7 @@
         public void dump(IndentingPrintWriter pw) {
             String line = String.format(Locale.ROOT,
                     "%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
-                    DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+                    DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
                     mOperation.name().toLowerCase(Locale.ROOT),
                     (mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
                     Long.toBinaryString(mTypesMask), createVibrationUsagesString());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 5175b74..0412fcb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 import static android.app.WallpaperManager.getOrientation;
 import static android.app.WallpaperManager.getRotatedOrientation;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -86,7 +87,7 @@
     public interface WallpaperCropUtils {
 
         /**
-         * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
+         * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}
          */
         Rect getCrop(Point displaySize, Point bitmapSize,
                 SparseArray<Rect> suggestedCrops, boolean rtl);
@@ -120,16 +121,44 @@
     public Rect getCrop(Point displaySize, Point bitmapSize,
             SparseArray<Rect> suggestedCrops, boolean rtl) {
 
-        // Case 1: if no crops are provided, center align the full image
+        int orientation = getOrientation(displaySize);
+
+        // Case 1: if no crops are provided, show the full image (from the left, or right if RTL).
         if (suggestedCrops == null || suggestedCrops.size() == 0) {
-            Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
-            float scale = Math.min(
-                    ((float) bitmapSize.x) / displaySize.x,
-                    ((float) bitmapSize.y) / displaySize.y);
-            crop.scale(scale);
-            crop.offset((bitmapSize.x - crop.width()) / 2,
-                    (bitmapSize.y - crop.height()) / 2);
-            return crop;
+            Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+            // The first exception is if the device is a foldable and we're on the folded screen.
+            // In that case, show the center of what's on the unfolded screen.
+            int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+            if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
+                // Let the system know that we're showing the full image on the unfolded screen
+                SparseArray<Rect> newSuggestedCrops = new SparseArray<>();
+                newSuggestedCrops.put(unfoldedOrientation, crop);
+                // This will fall into "Case 4" of this function and center the folded screen
+                return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
+            }
+
+            // The second exception is if we're on tablet and we're on portrait mode.
+            // In that case, center the wallpaper relatively to landscape and put some parallax.
+            boolean isTablet = mWallpaperDisplayHelper.isLargeScreen()
+                    && !mWallpaperDisplayHelper.isFoldable();
+            if (isTablet && displaySize.x < displaySize.y) {
+                Point rotatedDisplaySize = new Point(displaySize.y, displaySize.x);
+                // compute the crop on landscape (without parallax)
+                Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+                landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl);
+                // compute the crop on portrait at the center of the landscape crop
+                crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD);
+
+                // add some parallax (until the border of the landscape crop without parallax)
+                if (rtl) {
+                    crop.left = landscapeCrop.left;
+                } else {
+                    crop.right = landscapeCrop.right;
+                }
+            }
+
+            return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
         }
 
         // If any suggested crop is invalid, fallback to case 1
@@ -142,8 +171,6 @@
             }
         }
 
-        int orientation = getOrientation(displaySize);
-
         // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
         Rect suggestedCrop = suggestedCrops.get(orientation);
         if (suggestedCrop != null) {
@@ -168,11 +195,21 @@
         suggestedCrop = suggestedCrops.get(unfoldedOrientation);
         suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
         if (suggestedCrop != null) {
-            // only keep the visible part (without parallax)
+            // compute the visible part (without parallax) of the unfolded screen
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
-            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+            // compute the folded crop, at the center of the crop of the unfolded screen
+            Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+            // if we removed some width, add it back to add a parallax effect
+            if (res.width() < adjustedCrop.width()) {
+                if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
+                else res.right = Math.max(res.right, adjustedCrop.right);
+                // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
+                res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
+            }
+            return res;
         }
 
+
         // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
         // have the suggested crop of the relative folded orientation, reuse it by adding content.
         int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
@@ -274,11 +311,8 @@
             if (additionalWidthForParallax > MAX_PARALLAX) {
                 int widthToRemove = (int) Math.ceil(
                         (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
-                if (rtl) {
-                    adjustedCrop.left += widthToRemove;
-                } else {
-                    adjustedCrop.right -= widthToRemove;
-                }
+                adjustedCrop.left += widthToRemove / 2;
+                adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2;
             }
         } else {
             // TODO (b/281648899) the third case is not always correct, fix that.
@@ -366,6 +400,24 @@
      */
     SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
 
+        // If the suggested crops is single-element map with (ORIENTATION_UNKNOWN, cropHint),
+        // Crop the bitmap using the cropHint and compute the crops for cropped bitmap.
+        Rect cropHint = suggestedCrops.get(ORIENTATION_UNKNOWN);
+        if (cropHint != null) {
+            Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+            if (suggestedCrops.size() != 1 || !bitmapRect.contains(cropHint)) {
+                Slog.w(TAG, "Couldn't get default crops from suggested crops " + suggestedCrops
+                        + " for bitmap of size " + bitmapSize + "; ignoring suggested crops");
+                return getDefaultCrops(new SparseArray<>(), bitmapSize);
+            }
+            Point cropSize = new Point(cropHint.width(), cropHint.height());
+            SparseArray<Rect> relativeDefaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+            for (int i = 0; i < relativeDefaultCrops.size(); i++) {
+                relativeDefaultCrops.valueAt(i).offset(cropHint.left, cropHint.top);
+            }
+            return relativeDefaultCrops;
+        }
+
         SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
         boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
                 == View.LAYOUT_DIRECTION_RTL;
@@ -422,26 +474,74 @@
         } else {
             boolean needCrop = false;
             boolean needScale;
-            boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop;
 
             Point bitmapSize = new Point(options.outWidth, options.outHeight);
+            Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
 
+            if (multiCrop()) {
+                // Check that the suggested crops per screen orientation are all within the bitmap.
+                for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+                    int orientation = wallpaper.mCropHints.keyAt(i);
+                    Rect crop = wallpaper.mCropHints.valueAt(i);
+                    if (crop.isEmpty() || !bitmapRect.contains(crop)) {
+                        Slog.w(TAG, "Invalid crop " + crop + " for orientation " + orientation
+                                + " and bitmap size " + bitmapSize + "; clearing suggested crops.");
+                        wallpaper.mCropHints.clear();
+                        wallpaper.cropHint.set(bitmapRect);
+                        break;
+                    }
+                }
+            }
             final Rect cropHint;
-            if (multiCrop) {
-                SparseArray<Rect> defaultDisplayCrops =
-                        getDefaultCrops(wallpaper.mCropHints, bitmapSize);
-                // adapt the entries in wallpaper.mCropHints for the actual display
+
+            // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like
+            // a wallpaper with cropHints = null and  cropHint = rect.
+            Rect tempCropHint = wallpaper.mCropHints.get(ORIENTATION_UNKNOWN);
+            if (multiCrop() && tempCropHint != null) {
+                wallpaper.cropHint.set(tempCropHint);
+                wallpaper.mCropHints.clear();
+            }
+            if (multiCrop() && wallpaper.mCropHints.size() > 0) {
+                // Some suggested crops per screen orientation were provided,
+                // use them to compute the default crops for this device
+                SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+                // Adapt the provided crops to match the actual crops for the default display
                 SparseArray<Rect> updatedCropHints = new SparseArray<>();
                 for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
                     int orientation = wallpaper.mCropHints.keyAt(i);
-                    Rect defaultCrop = defaultDisplayCrops.get(orientation);
+                    Rect defaultCrop = defaultCrops.get(orientation);
                     if (defaultCrop != null) {
                         updatedCropHints.put(orientation, defaultCrop);
                     }
                 }
                 wallpaper.mCropHints = updatedCropHints;
-                cropHint = getTotalCrop(defaultDisplayCrops);
+
+                // Finally, compute the cropHint based on the default crops
+                cropHint = getTotalCrop(defaultCrops);
                 wallpaper.cropHint.set(cropHint);
+                if (DEBUG) {
+                    Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops
+                            + " based on suggested crops: " + wallpaper.mCropHints);
+                }
+            } else if (multiCrop()) {
+                // No crops per screen orientation were provided, but an overall cropHint may be
+                // defined in wallpaper.cropHint. Compute the default crops for the sub-image
+                // defined by the cropHint, then recompute the cropHint based on the default crops.
+                // If the cropHint is empty or invalid, ignore it and use the full image.
+                if (wallpaper.cropHint.isEmpty()) wallpaper.cropHint.set(bitmapRect);
+                if (!bitmapRect.contains(wallpaper.cropHint)) {
+                    Slog.w(TAG, "Ignoring wallpaper.cropHint = " + wallpaper.cropHint
+                            + "; not within the bitmap of size " + bitmapSize);
+                    wallpaper.cropHint.set(bitmapRect);
+                }
+                Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+                SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+                cropHint = getTotalCrop(defaultCrops);
+                cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
+                wallpaper.cropHint.set(cropHint);
+                if (DEBUG) {
+                    Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops);
+                }
             } else {
                 cropHint = new Rect(wallpaper.cropHint);
             }
@@ -455,11 +555,11 @@
             }
 
             // Empty crop means use the full image
-            if (cropHint.isEmpty()) {
+            if (!multiCrop() && cropHint.isEmpty()) {
                 cropHint.left = cropHint.top = 0;
                 cropHint.right = options.outWidth;
                 cropHint.bottom = options.outHeight;
-            } else {
+            } else if (!multiCrop()) {
                 // force the crop rect to lie within the measured bounds
                 int dx = cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0;
                 int dy = cropHint.bottom > options.outHeight
@@ -473,19 +573,19 @@
                 if (cropHint.top < 0) {
                     cropHint.top = 0;
                 }
-
-                // Don't bother cropping if what we're left with is identity
-                needCrop = (options.outHeight > cropHint.height()
-                        || options.outWidth > cropHint.width());
             }
 
+            // Don't bother cropping if what we're left with is identity
+            needCrop = (options.outHeight > cropHint.height()
+                    || options.outWidth > cropHint.width());
+
             // scale if the crop height winds up not matching the recommended metrics
             needScale = cropHint.height() > wpData.mHeight
                     || cropHint.height() > GLHelper.getMaxTextureSize()
                     || cropHint.width() > GLHelper.getMaxTextureSize();
 
             //make sure screen aspect ratio is preserved if width is scaled under screen size
-            if (needScale && !multiCrop) {
+            if (needScale && !multiCrop()) {
                 final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
                 final int newWidth = (int) (cropHint.width() * scaleByHeight);
                 if (newWidth < displayInfo.logicalWidth) {
@@ -543,7 +643,7 @@
                     final Rect estimateCrop = new Rect(cropHint);
                     estimateCrop.scale(1f / options.inSampleSize);
                     float hRatio = (float) wpData.mHeight / estimateCrop.height();
-                    if (multiCrop) {
+                    if (multiCrop()) {
                         // make sure the crop height is at most the display largest dimension
                         hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
                                 / estimateCrop.height();
@@ -557,7 +657,7 @@
                         if (DEBUG) {
                             Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
                         }
-                        if (multiCrop) {
+                        if (multiCrop()) {
                             // clear custom crop guidelines, fallback to system default
                             wallpaper.mCropHints.clear();
                             generateCropInternal(wallpaper);
@@ -618,7 +718,7 @@
                         final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
                                 safeWidth, safeHeight, true);
 
-                        if (multiCrop) {
+                        if (multiCrop()) {
                             wallpaper.mSampleSize =
                                     ((float) cropHint.height()) / finalCrop.getHeight();
                         }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 02594d2..b792f79 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -172,11 +172,6 @@
     SparseArray<Rect> mCropHints = new SparseArray<>();
 
     /**
-     * cropHints will be ignored if this flag is false
-     */
-    boolean mSupportsMultiCrop;
-
-    /**
      * The phone orientation when the wallpaper was set. Only relevant for image wallpapers
      */
     int mOrientationWhenSet = ORIENTATION_UNKNOWN;
@@ -204,7 +199,6 @@
         if (source.mCropHints != null) {
             this.mCropHints = source.mCropHints.clone();
         }
-        this.mSupportsMultiCrop = source.mSupportsMultiCrop;
         this.allowBackup = source.allowBackup;
         this.primaryColors = source.primaryColors;
         this.mWallpaperDimAmount = source.mWallpaperDimAmount;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 7f53ea3..4aefb54 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -324,10 +324,7 @@
                 getAttributeInt(parser, "totalCropTop", 0),
                 getAttributeInt(parser, "totalCropRight", 0),
                 getAttributeInt(parser, "totalCropBottom", 0));
-        wallpaper.mSupportsMultiCrop = multiCrop() && (
-                parser.getAttributeBoolean(null, "supportsMultiCrop", false)
-                || mImageWallpaper.equals(wallpaper.wallpaperComponent));
-        if (wallpaper.mSupportsMultiCrop) {
+        if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) {
             wallpaper.mCropHints = new SparseArray<>();
             for (Pair<Integer, String> pair: screenDimensionPairs()) {
                 Rect cropHint = new Rect(
@@ -342,16 +339,14 @@
             }
             if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
                 // migration case: the crops per screen orientation are not specified.
-                int orientation = legacyCropHint.width() < legacyCropHint.height()
-                        ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
                 if (!legacyCropHint.isEmpty()) {
-                    wallpaper.mCropHints.put(orientation, legacyCropHint);
+                    wallpaper.cropHint.set(legacyCropHint);
                 }
             } else {
                 wallpaper.cropHint.set(totalCropHint);
             }
             wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
-        } else {
+        } else if (!multiCrop()) {
             wallpaper.cropHint.set(legacyCropHint);
         }
         final DisplayData wpData = mWallpaperDisplayHelper
@@ -467,13 +462,12 @@
         out.startTag(null, tag);
         out.attributeInt(null, "id", wallpaper.wallpaperId);
 
-        out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop);
-
-        if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+        if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
             if (wallpaper.mCropHints == null) {
                 Slog.e(TAG, "cropHints should not be null when saved");
                 wallpaper.mCropHints = new SparseArray<>();
             }
+            Rect rectToPutInLegacyCrop = new Rect(wallpaper.cropHint);
             for (Pair<Integer, String> pair : screenDimensionPairs()) {
                 Rect cropHint = wallpaper.mCropHints.get(pair.first);
                 if (cropHint == null) continue;
@@ -493,12 +487,14 @@
                     }
                 }
                 if (pair.first == orientationToPutInLegacyCrop) {
-                    out.attributeInt(null, "cropLeft", cropHint.left);
-                    out.attributeInt(null, "cropTop", cropHint.top);
-                    out.attributeInt(null, "cropRight", cropHint.right);
-                    out.attributeInt(null, "cropBottom", cropHint.bottom);
+                    rectToPutInLegacyCrop.set(cropHint);
                 }
             }
+            out.attributeInt(null, "cropLeft", rectToPutInLegacyCrop.left);
+            out.attributeInt(null, "cropTop", rectToPutInLegacyCrop.top);
+            out.attributeInt(null, "cropRight", rectToPutInLegacyCrop.right);
+            out.attributeInt(null, "cropBottom", rectToPutInLegacyCrop.bottom);
+
             out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
             out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
             out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 9e1b5d2..3636f5a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -59,6 +59,8 @@
     }
 
     private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
+    private static final float LARGE_SCREEN_MIN_DP = 600f;
+
     private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
     private final DisplayManager mDisplayManager;
     private final WindowManagerInternal mWindowManagerInternal;
@@ -67,7 +69,8 @@
     // related orientations pairs for foldable (folded orientation, unfolded orientation)
     private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
 
-    private boolean mIsFoldable;
+    private final boolean mIsFoldable;
+    private boolean mIsLargeScreen = false;
 
     WallpaperDisplayHelper(
             DisplayManager displayManager,
@@ -94,6 +97,9 @@
                     mDefaultDisplaySizes.put(orientation, point);
                 }
             }
+
+            mIsLargeScreen |= (displaySize.x / metric.getDensity() >= LARGE_SCREEN_MIN_DP);
+
             if (populateOrientationPairs) {
                 int orientation = WallpaperManager.getOrientation(displaySize);
                 float newSurface = displaySize.x * displaySize.y
@@ -215,6 +221,13 @@
     }
 
     /**
+     * Return true if any of the screens of the default display is considered large (DP >= 600)
+     */
+    boolean isLargeScreen() {
+        return mIsLargeScreen;
+    }
+
+    /**
      * If a given orientation corresponds to an unfolded orientation on foldable, return the
      * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
      * device is not a foldable.
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 885baf6..9a5961a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
 import static com.android.window.flags.Flags.multiCrop;
+import static com.android.window.flags.Flags.offloadColorExtraction;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -131,6 +132,9 @@
 import com.android.server.wallpaper.WallpaperData.BindSource;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -166,6 +170,13 @@
         }
 
         @Override
+        @UsesReflection(
+                value = {
+                    @KeepTarget(
+                            kind = KeepItemKind.CLASS_AND_MEMBERS,
+                            instanceOfClassConstantExclusive = IWallpaperManagerService.class,
+                            methodName = "<init>")
+                })
         public void onStart() {
             try {
                 final Class<? extends IWallpaperManagerService> klass =
@@ -380,7 +391,7 @@
             }
 
             // Outside of the lock since it will synchronize itself
-            notifyWallpaperColorsChanged(wallpaper);
+            if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wallpaper);
         }
 
         @Override
@@ -403,12 +414,16 @@
     }
 
     void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) {
+        notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich);
+    }
+
+    private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
         if (DEBUG) {
             Slog.i(TAG, "Notifying wallpaper colors changed");
         }
         if (wallpaper.connection != null) {
             wallpaper.connection.forEachDisplayConnector(connector ->
-                    notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId));
+                    notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId, which));
         }
     }
 
@@ -425,6 +440,11 @@
 
     private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
             int displayId) {
+        notifyWallpaperColorsChangedOnDisplay(wallpaper, displayId, wallpaper.mWhich);
+    }
+
+    private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
+            int displayId, int which) {
         boolean needsExtraction;
         synchronized (mLock) {
             final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
@@ -449,8 +469,8 @@
             notify = extractColors(wallpaper);
         }
         if (notify) {
-            notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper),
-                    wallpaper.mWhich, wallpaper.userId, displayId);
+            notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
+                    wallpaper.userId, displayId);
         }
     }
 
@@ -504,6 +524,7 @@
      * @return true unless the wallpaper changed during the color computation
      */
     private boolean extractColors(WallpaperData wallpaper) {
+        if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent);
         String cropFile = null;
         boolean defaultImageWallpaper = false;
         int wallpaperId;
@@ -803,7 +824,7 @@
                     null /* options */);
             mWindowManagerInternal.setWallpaperShowWhenLocked(
                     mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
-            if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+            if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
                 mWindowManagerInternal.setWallpaperCropHints(mToken,
                         mWallpaperCropper.getRelativeCropHints(wallpaper));
             } else {
@@ -1148,10 +1169,16 @@
             synchronized (mLock) {
                 // Do not broadcast changes on ImageWallpaper since it's handled
                 // internally by this class.
-                if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
+                boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent);
+                if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) {
                     return;
                 }
                 mWallpaper.primaryColors = primaryColors;
+                // only save the colors for ImageWallpaper - for live wallpapers, the colors
+                // are always recomputed after a reboot.
+                if (offloadColorExtraction() && isImageWallpaper) {
+                    saveSettingsLocked(mWallpaper.userId);
+                }
             }
             notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId);
         }
@@ -1177,7 +1204,9 @@
                 try {
                     // This will trigger onComputeColors in the wallpaper engine.
                     // It's fine to be locked in here since the binder is oneway.
-                    connector.mEngine.requestWallpaperColors();
+                    if (!offloadColorExtraction() || mWallpaper.primaryColors == null) {
+                        connector.mEngine.requestWallpaperColors();
+                    }
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to request wallpaper colors", e);
                 }
@@ -1811,6 +1840,7 @@
             // Offload color extraction to another thread since switchUser will be called
             // from the main thread.
             FgThread.getHandler().post(() -> {
+                if (offloadColorExtraction()) return;
                 notifyWallpaperColorsChanged(systemWallpaper);
                 if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper);
                 notifyWallpaperColorsChanged(mFallbackWallpaper);
@@ -1917,11 +1947,17 @@
             final ComponentName component;
             final int finalWhich;
 
-            if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) {
-                clearWallpaperBitmaps(lockWallpaper);
-            }
-            if ((which & FLAG_SYSTEM) > 0) {
-                clearWallpaperBitmaps(wallpaper);
+            // Clear any previous ImageWallpaper related fields
+            List<WallpaperData> toClear = new ArrayList<>();
+            if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
+            if ((which & FLAG_SYSTEM) > 0) toClear.add(wallpaper);
+            for (WallpaperData wallpaperToClear : toClear) {
+                clearWallpaperBitmaps(wallpaperToClear);
+                if (multiCrop()) {
+                    wallpaperToClear.mCropHints.clear();
+                    wallpaperToClear.cropHint.set(0, 0, 0, 0);
+                    wallpaperToClear.mSampleSize = 1;
+                }
             }
 
             // lock only case: set the system wallpaper component to both screens
@@ -2225,7 +2261,9 @@
             checkPermission(READ_WALLPAPER_INTERNAL);
             WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
                     : mWallpaperMap.get(userId);
-            if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
+            if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+                return null;
+            }
             SparseArray<Rect> relativeSuggestedCrops =
                     mWallpaperCropper.getRelativeCropHints(wallpaper);
             Point croppedBitmapSize = new Point(
@@ -2255,7 +2293,7 @@
     @Override
     public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
             int[] screenOrientations, List<Rect> crops) {
-        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
         SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
         List<Rect> result = new ArrayList<>();
         boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
@@ -2272,7 +2310,7 @@
             throw new UnsupportedOperationException(
                     "This method should only be called with the multi crop flag enabled");
         }
-        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
         SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
         return WallpaperCropper.getTotalCrop(defaultCrops);
     }
@@ -2714,8 +2752,10 @@
                         });
                         // Need to extract colors again to re-calculate dark hints after
                         // applying dimming.
-                        wp.mIsColorExtractedFromDim = true;
-                        pendingColorExtraction.add(wp);
+                        if (!offloadColorExtraction()) {
+                            wp.mIsColorExtractedFromDim = true;
+                            pendingColorExtraction.add(wp);
+                        }
                         changed = true;
                     }
                 }
@@ -2724,7 +2764,7 @@
                 }
             }
             for (WallpaperData wp: pendingColorExtraction) {
-                notifyWallpaperColorsChanged(wp);
+                if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wp);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -2860,10 +2900,8 @@
             return null;
         }
 
-        int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
-        SparseArray<Rect> cropMap = !multiCrop() ? null
-                : getCropMap(screenOrientations, crops, currentOrientation);
-        Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
+        SparseArray<Rect> cropMap = !multiCrop() ? null : getCropMap(screenOrientations, crops);
+        Rect cropHint = multiCrop() || crops == null || crops.isEmpty() ? new Rect() : crops.get(0);
         final boolean fromForegroundApp = !multiCrop() ? false
                 : isFromForegroundApp(callingPackage);
 
@@ -2912,12 +2950,16 @@
                     wallpaper.setComplete = completion;
                     wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
                             : isFromForegroundApp(callingPackage);
-                    if (!multiCrop()) wallpaper.cropHint.set(cropHint);
-                    if (multiCrop()) wallpaper.mSupportsMultiCrop = true;
-                    if (multiCrop()) wallpaper.mCropHints = cropMap;
+                    wallpaper.cropHint.set(cropHint);
+                    if (multiCrop()) {
+                        wallpaper.mCropHints = cropMap;
+                        wallpaper.mSampleSize = 1f;
+                        wallpaper.mOrientationWhenSet =
+                                mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
+                    }
                     wallpaper.allowBackup = allowBackup;
                     wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
-                    wallpaper.mOrientationWhenSet = currentOrientation;
+                    if (offloadColorExtraction()) wallpaper.primaryColors = null;
                 }
                 return pfd;
             } finally {
@@ -2926,16 +2968,14 @@
         }
     }
 
-    private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
-            int currentOrientation) {
+    private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops) {
         if ((crops == null ^ screenOrientations == null)
                 || (crops != null && crops.size() != screenOrientations.length)) {
             throw new IllegalArgumentException(
                     "Illegal crops/orientations lists: must both be null, or both the same size");
         }
         SparseArray<Rect> cropMap = new SparseArray<>();
-        boolean unknown = false;
-        if (crops != null && crops.size() != 0) {
+        if (crops != null && !crops.isEmpty()) {
             for (int i = 0; i < crops.size(); i++) {
                 Rect crop = crops.get(i);
                 int width = crop.width(), height = crop.height();
@@ -2943,22 +2983,13 @@
                     throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
                 }
                 int orientation = screenOrientations[i];
-                if (orientation == ORIENTATION_UNKNOWN) {
-                    if (currentOrientation == ORIENTATION_UNKNOWN) {
-                        throw new IllegalArgumentException(
-                                "Invalid orientation: " + ORIENTATION_UNKNOWN);
-                    }
-                    unknown = true;
-                    orientation = currentOrientation;
+                if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+                    throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
+                            + "screen orientation should only be used in a singleton map");
                 }
                 cropMap.put(orientation, crop);
             }
         }
-        if (unknown && cropMap.size() > 1) {
-            throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
-                    + "orientation should only be used in a singleton map (in which case it"
-                    + "represents the current orientation of the default display)");
-        }
         return cropMap;
     }
 
@@ -2975,7 +3006,6 @@
         WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK);
         lockWP.wallpaperId = sysWP.wallpaperId;
         lockWP.cropHint.set(sysWP.cropHint);
-        lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop;
         if (sysWP.mCropHints != null) {
             lockWP.mCropHints = sysWP.mCropHints.clone();
         }
@@ -3072,6 +3102,10 @@
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
         boolean shouldNotifyColors = false;
+
+        // If the lockscreen wallpaper is set to the same as the home screen, notify that the
+        // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
+        boolean shouldNotifyLockscreenColors = false;
         boolean bindSuccess;
         final WallpaperData newWallpaper;
 
@@ -3096,7 +3130,6 @@
             final long ident = Binder.clearCallingIdentity();
 
             try {
-                newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name);
                 newWallpaper.imageWallpaperPending = false;
                 newWallpaper.mWhich = which;
                 newWallpaper.mSystemWasBoth = systemIsBoth;
@@ -3118,7 +3151,7 @@
                 bindSuccess = bindWallpaperComponentLocked(name, /* force */
                         forceRebind, /* fromUser */ true, newWallpaper, reply);
                 if (bindSuccess) {
-                    if (!same) {
+                    if (!same || (offloadColorExtraction() && forceRebind)) {
                         newWallpaper.primaryColors = null;
                     } else {
                         if (newWallpaper.connection != null) {
@@ -3142,6 +3175,11 @@
                     newWallpaper.wallpaperId = makeWallpaperIdLocked();
                     notifyCallbacksLocked(newWallpaper);
                     shouldNotifyColors = true;
+                    if (offloadColorExtraction()) {
+                        shouldNotifyColors = false;
+                        shouldNotifyLockscreenColors = !force && same && !systemIsBoth
+                                && which == (FLAG_SYSTEM | FLAG_LOCK);
+                    }
 
                     if (which == (FLAG_SYSTEM | FLAG_LOCK)) {
                         if (DEBUG) {
@@ -3170,6 +3208,10 @@
         if (shouldNotifyColors) {
             notifyWallpaperColorsChanged(newWallpaper);
         }
+        if (shouldNotifyLockscreenColors) {
+            notifyWallpaperColorsChanged(newWallpaper, FLAG_LOCK);
+        }
+
         return bindSuccess;
     }
 
diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig
index 84dc1d7..2afbcd6 100644
--- a/services/core/java/com/android/server/webkit/flags.aconfig
+++ b/services/core/java/com/android/server/webkit/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.webkit"
-container: "system"
 
 flag {
     name: "update_service_v2"
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4036d55..207d42b6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8645,7 +8645,7 @@
 
         // Override starts here.
         final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
-                rotation, fullBounds.width(), fullBounds.height()).mLegacyConfigInsets;
+                rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
         // This should be the only place override the configuration for ActivityRecord. Override
         // the value if not calculated yet.
         Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -8681,7 +8681,7 @@
             mDisplayContent.getDisplay().getDisplayInfo(info);
             mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
                     info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
-                    inOutConfig, true /* legacyConfig */);
+                    inOutConfig, true /* overrideConfig */);
         }
 
         // It's possible that screen size will be considered in different orientation with or
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f6681c5..f7b4a67 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -20,6 +20,7 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -779,6 +780,10 @@
                     && wc.asTaskFragment() == null) {
                 continue;
             }
+            // Only care if visibility changed.
+            if (targets.get(i).getTransitMode(wc) == TRANSIT_CHANGE) {
+                continue;
+            }
             // WC can be visible due to setLaunchBehind
             if (wc.isVisibleRequested()) {
                 mTmpOpenApps.add(wc);
@@ -843,14 +848,14 @@
      * @param targets The final animation targets derived in transition.
      * @param finishedTransition The finished transition target.
     */
-    boolean onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
+    void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
             @NonNull Transition finishedTransition) {
         if (finishedTransition == mWaitTransitionFinish) {
             clearBackAnimations();
         }
 
         if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
-            return false;
+            return;
         }
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Handling the deferred animation after transition finished");
@@ -878,7 +883,7 @@
                     + " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
                     + " close: " + mPendingAnimationBuilder.mCloseTarget);
             cancelPendingAnimation();
-            return false;
+            return;
         }
 
         // Ensure the final animation targets which hidden by transition could be visible.
@@ -887,9 +892,14 @@
             wc.prepareSurfaces();
         }
 
-        scheduleAnimation(mPendingAnimationBuilder);
-        mPendingAnimationBuilder = null;
-        return true;
+        // The pending builder could be cleared due to prepareSurfaces
+        // => updateNonSystemOverlayWindowsVisibilityIfNeeded
+        // => setForceHideNonSystemOverlayWindowIfNeeded
+        // => updateFocusedWindowLocked => onFocusWindowChanged.
+        if (mPendingAnimationBuilder != null) {
+            scheduleAnimation(mPendingAnimationBuilder);
+            mPendingAnimationBuilder = null;
+        }
     }
 
     private void cancelPendingAnimation() {
@@ -1552,15 +1562,17 @@
                 return this;
             }
 
+            // WC must be Activity/TaskFragment/Task
             boolean containTarget(@NonNull WindowContainer wc) {
                 if (mOpenTargets != null) {
                     for (int i = mOpenTargets.length - 1; i >= 0; --i) {
-                        if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) {
+                        if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)
+                                || wc.hasChild(mOpenTargets[i])) {
                             return true;
                         }
                     }
                 }
-                return wc == mCloseTarget || mCloseTarget.hasChild(wc);
+                return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
             }
 
             /**
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 47f4a66..0e446b8 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -38,9 +38,11 @@
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
+import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
 import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
 import static com.android.window.flags.Flags.balRequireOptInSameUid;
+import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -539,6 +541,16 @@
                 sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
                 sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
             }
+            // features
+            sb.append("; balImproveRealCallerVisibilityCheck: ")
+                    .append(balImproveRealCallerVisibilityCheck());
+            sb.append("; balRequireOptInByPendingIntentCreator: ")
+                    .append(balRequireOptInByPendingIntentCreator());
+            sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
+            sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
+                    .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
+            sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
+                    .append(balDontBringExistingBackgroundTaskStackToFg());
             sb.append("]");
             return sb.toString();
         }
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index efeb85f..d49a507 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -40,6 +40,10 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -184,6 +188,13 @@
         /**
          * Instantiates the device-specific {@link Provider}.
          */
+        @UsesReflection(
+                value = {
+                        @KeepTarget(
+                                kind = KeepItemKind.CLASS_AND_MEMBERS,
+                                instanceOfClassConstantExclusive = Provider.class,
+                                methodName = "<init>")
+                })
         static Provider fromResources(Resources res) {
             String name = res.getString(
                     com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cde3e68..739f76e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1007,8 +1007,6 @@
 
     private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
         final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
-        final boolean obscuredChanged = w.mObscured !=
-                mTmpApplySurfaceChangesTransactionState.obscured;
         final RootWindowContainer root = mWmService.mRoot;
 
         if (w.mHasSurface) {
@@ -1107,12 +1105,6 @@
             }
         }
 
-        if (obscuredChanged && w.isVisible() && mWallpaperController.isWallpaperTarget(w)) {
-            // This is the wallpaper target and its obscured state changed... make sure the
-            // current wallpaper's visibility has been updated accordingly.
-            mWallpaperController.updateWallpaperTokens(mDisplayContent.isKeyguardLocked());
-        }
-
         w.handleWindowMovedIfNeeded();
 
         //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
@@ -2277,7 +2269,7 @@
         }
 
         computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
-                false /* legacyConfig */);
+                false /* overrideConfig */);
 
         setDisplayInfoOverride();
 
@@ -2414,7 +2406,7 @@
         final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
         displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
         computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
-                false /* legacyConfig */);
+                false /* overrideConfig */);
         return displayInfo;
     }
 
@@ -2588,11 +2580,10 @@
      * @param dh Display Height in current rotation.
      * @param density Display density.
      * @param outConfig The output configuration to
-     * @param legacyConfig Whether we need to report the legacy result, which is excluding system
-     *                     decorations.
+     * @param overrideConfig Whether we need to report the override config result
      */
     void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
-            int dw, int dh, float density, Configuration outConfig, boolean legacyConfig) {
+            int dw, int dh, float density, Configuration outConfig, boolean overrideConfig) {
 
         // We need to determine the smallest width that will occur under normal
         // operation.  To this, start with the base screen size and compute the
@@ -2610,10 +2601,12 @@
         displayInfo.smallestNominalAppHeight = 1<<30;
         displayInfo.largestNominalAppWidth = 0;
         displayInfo.largestNominalAppHeight = 0;
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, legacyConfig);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, legacyConfig);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh, legacyConfig);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw, legacyConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, overrideConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, overrideConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh,
+                overrideConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw,
+                overrideConfig);
 
         if (outConfig == null) {
             return;
@@ -2623,17 +2616,17 @@
     }
 
     private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh,
-            boolean legacyConfig) {
+            boolean overrideConfig) {
         final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
                 rotation, dw, dh);
         final int w;
         final int h;
-        if (!legacyConfig) {
+        if (!overrideConfig) {
             w = info.mConfigFrame.width();
             h = info.mConfigFrame.height();
         } else {
-            w = info.mLegacyConfigFrame.width();
-            h = info.mLegacyConfigFrame.height();
+            w = info.mOverrideConfigFrame.width();
+            h = info.mOverrideConfigFrame.height();
         }
         if (w < displayInfo.smallestNominalAppWidth) {
             displayInfo.smallestNominalAppWidth = w;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a5037ea..5e0d4f9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1935,9 +1935,9 @@
             final Rect mConfigInsets = new Rect();
 
             /**
-             * Legacy value of mConfigInsets for app compatibility purpose.
+             * Override value of mConfigInsets for app compatibility purpose.
              */
-            final Rect mLegacyConfigInsets = new Rect();
+            final Rect mOverrideConfigInsets = new Rect();
 
             /** The display frame available after excluding {@link #mNonDecorInsets}. */
             final Rect mNonDecorFrame = new Rect();
@@ -1950,9 +1950,9 @@
             final Rect mConfigFrame = new Rect();
 
             /**
-             * Legacy value of mConfigFrame for app compatibility purpose.
+             * Override value of mConfigFrame for app compatibility purpose.
              */
-            final Rect mLegacyConfigFrame = new Rect();
+            final Rect mOverrideConfigFrame = new Rect();
 
             private boolean mNeedUpdate = true;
 
@@ -1968,22 +1968,22 @@
                         ? decor
                         : insetsState.calculateInsets(displayFrame, dc.mWmService.mConfigTypes,
                                 true /* ignoreVisibility */);
-                final Insets legacyConfigInsets = dc.mWmService.mConfigTypes
-                        == dc.mWmService.mLegacyConfigTypes
+                final Insets overrideConfigInsets = dc.mWmService.mConfigTypes
+                        == dc.mWmService.mOverrideConfigTypes
                         ? configInsets
                         : insetsState.calculateInsets(displayFrame,
-                                dc.mWmService.mLegacyConfigTypes, true /* ignoreVisibility */);
+                                dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
                 mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
                 mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
                         configInsets.bottom);
-                mLegacyConfigInsets.set(legacyConfigInsets.left, legacyConfigInsets.top,
-                        legacyConfigInsets.right, legacyConfigInsets.bottom);
+                mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
+                        overrideConfigInsets.right, overrideConfigInsets.bottom);
                 mNonDecorFrame.set(displayFrame);
                 mNonDecorFrame.inset(mNonDecorInsets);
                 mConfigFrame.set(displayFrame);
                 mConfigFrame.inset(mConfigInsets);
-                mLegacyConfigFrame.set(displayFrame);
-                mLegacyConfigFrame.inset(mLegacyConfigInsets);
+                mOverrideConfigFrame.set(displayFrame);
+                mOverrideConfigFrame.inset(mOverrideConfigInsets);
                 mNeedUpdate = false;
                 return insetsState;
             }
@@ -1991,10 +1991,10 @@
             void set(Info other) {
                 mNonDecorInsets.set(other.mNonDecorInsets);
                 mConfigInsets.set(other.mConfigInsets);
-                mLegacyConfigInsets.set(other.mLegacyConfigInsets);
+                mOverrideConfigInsets.set(other.mOverrideConfigInsets);
                 mNonDecorFrame.set(other.mNonDecorFrame);
                 mConfigFrame.set(other.mConfigFrame);
-                mLegacyConfigFrame.set(other.mLegacyConfigFrame);
+                mOverrideConfigFrame.set(other.mOverrideConfigFrame);
                 mNeedUpdate = false;
             }
 
@@ -2107,7 +2107,7 @@
         final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
         final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
         if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
-                && newInfo.mLegacyConfigFrame.equals(currentInfo.mLegacyConfigFrame)) {
+                && newInfo.mOverrideConfigFrame.equals(currentInfo.mOverrideConfigFrame)) {
             // Even if the config frame is not changed in current rotation, it may change the
             // insets in other rotations if the frame of insets source is changed.
             final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b8bb258..0ad601d 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -61,6 +61,7 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 
@@ -225,13 +226,16 @@
             if (keyguardShowing) {
                 state.mDismissalRequested = false;
             }
-            if (goingAwayRemoved) {
-                // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up
-                // before holding the sleep token again.
+            if (goingAwayRemoved || (keyguardShowing && Flags.keyguardAppearTransition())) {
+                // Keyguard decided to show or stopped going away. Send a transition to animate back
+                // to the locked state before holding the sleep token again
                 final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
                 dc.requestTransitionAndLegacyPrepare(
                         TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
-                mWindowManager.executeAppTransition();
+                if (Flags.keyguardAppearTransition()) {
+                    dc.mWallpaperController.adjustWallpaperWindows();
+                }
+                dc.executeAppTransition();
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index bd1503f..129af90 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2427,7 +2427,7 @@
         if (!useLegacyInsetsForStableBounds) {
             intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
         } else {
-            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mLegacyConfigInsets);
+            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index c8cb934..65e1761 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -856,19 +856,12 @@
         result.setWallpaperTarget(wallpaperTarget);
     }
 
-    public void updateWallpaperTokens(boolean keyguardLocked) {
-        if (DEBUG_WALLPAPER) {
-            Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev="
-                    + mPrevWallpaperTarget);
-        }
-        updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null,
-                keyguardLocked);
-    }
-
     /**
      * Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
      */
     private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) {
+        ProtoLog.v(WM_DEBUG_WALLPAPER, "updateWallpaperTokens requestedVisibility=%b on"
+                + " keyguardLocked=%b", visibility, keyguardLocked);
         WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked);
         WallpaperWindowToken topWallpaperToken =
                 topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 55eeaf2..5c24eee 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -274,6 +274,12 @@
     }
 
     @Override
+    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+        // TODO(b/233286785): Support sync state for wallpaper. See WindowState#prepareSync.
+        return !mVisibleRequested || !hasVisibleNotDrawnWallpaper();
+    }
+
+    @Override
     public String toString() {
         if (stringName == null) {
             StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2e72121..ed88b5a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -241,6 +241,7 @@
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.MergedConfiguration;
 import android.util.Pair;
 import android.util.Slog;
@@ -567,7 +568,7 @@
     /** Device default insets types shall be excluded from config app sizes. */
     final int mConfigTypes;
 
-    final int mLegacyConfigTypes;
+    final int mOverrideConfigTypes;
 
     final boolean mLimitedAlphaCompositing;
     final int mMaxUiWidth;
@@ -1106,6 +1107,14 @@
 
     @GuardedBy("mGlobalLock")
     final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages();
+    /**
+     * UIDs for which a Toast has been shown to indicate
+     * {@link LocalService#addBlockScreenCaptureForApps(ArraySet) screen capture blocking}. This is
+     * used to ensure we don't keep re-showing the Toast every time the window becomes visible.
+     * UIDs are removed when the app is removed from the block list.
+     */
+    @GuardedBy("mGlobalLock")
+    private final IntArray mCaptureBlockedToastShownUids = new IntArray();
 
     /** Listener to notify activity manager about app transitions. */
     final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
@@ -1238,20 +1247,18 @@
         if (mFlags.mInsetsDecoupledConfiguration) {
             mDecorTypes = 0;
             mConfigTypes = 0;
-        } else if (isScreenSizeDecoupledFromStatusBarAndCutout) {
-            mDecorTypes = WindowInsets.Type.navigationBars();
-            mConfigTypes = WindowInsets.Type.navigationBars();
         } else {
             mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
             mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
                     | WindowInsets.Type.navigationBars();
         }
-        if (isScreenSizeDecoupledFromStatusBarAndCutout) {
-            // Do not fallback to legacy value for enabled devices.
-            mLegacyConfigTypes = WindowInsets.Type.navigationBars();
+        if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
+            // If the global new behavior is not there, but the partial decouple flag is on.
+            mOverrideConfigTypes = 0;
         } else {
-            mLegacyConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
-                    | WindowInsets.Type.navigationBars();
+            mOverrideConfigTypes =
+                    WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+                            | WindowInsets.Type.navigationBars();
         }
 
         mLetterboxConfiguration = new LetterboxConfiguration(
@@ -3685,12 +3692,6 @@
 
     // Called by window manager policy.  Not exposed externally.
     @Override
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        mInputManager.switchKeyboardLayout(deviceId, direction);
-    }
-
-    // Called by window manager policy.  Not exposed externally.
-    @Override
     public void shutdown(boolean confirm) {
         // Pass in the UI context, since ShutdownThread requires it (to show UI).
         ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
@@ -8754,6 +8755,15 @@
                 if (modified) {
                     WindowManagerService.this.refreshScreenCaptureDisabled();
                 }
+                if (sensitiveContentImprovements()) {
+                    for (int i = 0; i < packageInfos.size(); i++) {
+                        int uid = packageInfos.valueAt(i).getUid();
+                        if (mCaptureBlockedToastShownUids.contains(uid)) {
+                            mCaptureBlockedToastShownUids.remove(
+                                    mCaptureBlockedToastShownUids.indexOf(uid));
+                        }
+                    }
+                }
             }
         }
 
@@ -8764,6 +8774,9 @@
                 if (modified) {
                     WindowManagerService.this.refreshScreenCaptureDisabled();
                 }
+                if (sensitiveContentImprovements()) {
+                    mCaptureBlockedToastShownUids.clear();
+                }
             }
         }
 
@@ -10165,13 +10178,19 @@
      * on sensitive content protections.
      */
     private void showToastIfBlockingScreenCapture(@NonNull WindowState w) {
-        // TODO(b/323580163): Check if already shown and update shown state.
-        if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(),
-                w.getOwningUid(), w.getWindowToken())) {
-            Toast.makeText(mContext, Looper.getMainLooper(),
-                            mContext.getString(R.string.screen_not_shared_sensitive_content),
-                            Toast.LENGTH_SHORT)
-                    .show();
+        int uid = w.getOwningUid();
+        if (mCaptureBlockedToastShownUids.contains(uid)) {
+            return;
+        }
+        if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(), uid,
+                w.getWindowToken())) {
+            mCaptureBlockedToastShownUids.add(uid);
+            mH.post(() -> {
+                Toast.makeText(mContext, Looper.getMainLooper(),
+                                mContext.getString(R.string.screen_not_shared_sensitive_content),
+                                Toast.LENGTH_SHORT)
+                        .show();
+            });
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6ac2774..9d8246d 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -110,8 +110,8 @@
     private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
 
-    private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 500;
-    private static final long RAPID_ACTIVITY_LAUNCH_MS = 300;
+    private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 50;
+    private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
     private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 5 * RAPID_ACTIVITY_LAUNCH_MS;
 
     public static final int STOPPED_STATE_NOT_STOPPED = 0;
@@ -202,6 +202,12 @@
     // Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
     private volatile boolean mHasImeService;
 
+    /**
+     * Whether this process can use realtime prioirity (SCHED_FIFO) for its UI and render threads
+     * when this process is SCHED_GROUP_TOP_APP.
+     */
+    private final boolean mUseFifoUiScheduling;
+
     /** Whether {@link #mActivities} is not empty. */
     private volatile boolean mHasActivities;
     /** All activities running in the process (exclude destroying). */
@@ -340,6 +346,8 @@
             // TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
             mIsActivityConfigOverrideAllowed = false;
         }
+        mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
+                && (isSysUiPackage || mAtm.isCallerRecents(uid));
 
         mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
                 && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
@@ -631,9 +639,15 @@
         }
 
         if (mRapidActivityLaunchCount > MAX_RAPID_ACTIVITY_LAUNCH_COUNT) {
-            Slog.w(TAG, "Killing " + mPid + " because of rapid activity launch");
-            r.getRootTask().moveTaskToBack(r.getTask());
-            mAtm.mH.post(() -> mAtm.mAmInternal.killProcess(mName, mUid, "rapidActivityLaunch"));
+            mRapidActivityLaunchCount = 0;
+            final Task task = r.getTask();
+            Slog.w(TAG, "Removing task " + task.mTaskId + " because of rapid activity launch");
+            mAtm.mH.post(() -> {
+                synchronized (mAtm.mGlobalLock) {
+                    task.removeImmediately("rapid-activity-launch");
+                }
+                mAtm.mAmInternal.killProcess(mName, mUid, "rapidActivityLaunch");
+            });
         }
     }
 
@@ -1901,6 +1915,11 @@
         }
     }
 
+    /** Returns {@code true} if the process prefers to use fifo scheduling. */
+    public boolean useFifoUiScheduling() {
+        return mUseFifoUiScheduling;
+    }
+
     @HotPath(caller = HotPath.OOM_ADJUSTMENT)
     public void onTopProcChanged() {
         if (mAtm.mVrController.isInterestingToSchedGroup()) {
@@ -2078,6 +2097,9 @@
             }
             pw.println();
         }
+        if (mUseFifoUiScheduling) {
+            pw.println(prefix + " mUseFifoUiScheduling=true");
+        }
 
         final int stateFlags = mActivityStateFlags;
         if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 8598023..f1962cb 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -226,11 +226,8 @@
             <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
                 maxOccurs="1">
             </xs:element>
-            <xs:element name="nits" type="xs:float" maxOccurs="unbounded">
-            </xs:element>
-            <xs:element name="backlight" type="xs:float" maxOccurs="unbounded">
-            </xs:element>
-            <xs:element name="brightness" type="xs:float" maxOccurs="unbounded">
+            <!-- Mapping of nits -> backlight -> brightness -->
+            <xs:element name="brightnessMapping" type="comprehensiveBrightnessMap" maxOccurs="1">
             </xs:element>
             <!-- Mapping of current lux to minimum allowed nits values. -->
             <xs:element name="luxToMinimumNitsMap" type="nitsMap" maxOccurs="1">
@@ -449,6 +446,35 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="comprehensiveBrightnessMap">
+        <xs:sequence>
+            <xs:element name="brightnessPoint" type="brightnessPoint" maxOccurs="unbounded" minOccurs="2">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+        <!-- valid value of interpolation if specified: linear -->
+        <xs:attribute name="interpolation" type="xs:string" use="optional"/>
+    </xs:complexType>
+
+    <xs:complexType name="brightnessPoint">
+        <xs:sequence>
+            <xs:element type="nonNegativeDecimal" name="nits">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="nonNegativeDecimal" name="backlight">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="nonNegativeDecimal" name="brightness">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+
     <xs:complexType name="sdrHdrRatioMap">
         <xs:sequence>
             <xs:element name="point" type="sdrHdrRatioPoint" maxOccurs="unbounded" minOccurs="2">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 4ce4cc3..170434c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -53,6 +53,16 @@
     method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
   }
 
+  public class BrightnessPoint {
+    ctor public BrightnessPoint();
+    method @NonNull public final java.math.BigDecimal getBacklight();
+    method @NonNull public final java.math.BigDecimal getBrightness();
+    method @NonNull public final java.math.BigDecimal getNits();
+    method public final void setBacklight(@NonNull java.math.BigDecimal);
+    method public final void setBrightness(@NonNull java.math.BigDecimal);
+    method public final void setNits(@NonNull java.math.BigDecimal);
+  }
+
   public class BrightnessThresholds {
     ctor public BrightnessThresholds();
     method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -76,6 +86,13 @@
     method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus);
   }
 
+  public class ComprehensiveBrightnessMap {
+    ctor public ComprehensiveBrightnessMap();
+    method @NonNull public final java.util.List<com.android.server.display.config.BrightnessPoint> getBrightnessPoint();
+    method public String getInterpolation();
+    method public void setInterpolation(String);
+  }
+
   public class Density {
     ctor public Density();
     method @NonNull public final java.math.BigInteger getDensity();
@@ -183,12 +200,11 @@
 
   public class EvenDimmerMode {
     ctor public EvenDimmerMode();
-    method public java.util.List<java.lang.Float> getBacklight();
-    method public java.util.List<java.lang.Float> getBrightness();
+    method public com.android.server.display.config.ComprehensiveBrightnessMap getBrightnessMapping();
     method public boolean getEnabled();
     method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap();
-    method public java.util.List<java.lang.Float> getNits();
     method public java.math.BigDecimal getTransitionPoint();
+    method public void setBrightnessMapping(com.android.server.display.config.ComprehensiveBrightnessMap);
     method public void setEnabled(boolean);
     method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap);
     method public void setTransitionPoint(java.math.BigDecimal);
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index 0d5534b..b8cb4a9 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -26,5 +26,12 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      // TODO(b/332974906): Promote in presubmit presubmit-devicepolicy.
+      "name": "CtsDevicePolicyManagerTestCases_NoFlakes_NoLarge",
+      "name": "CtsDevicePolicyManagerTestCases_ParentProfileApiDisabled"
+    }
   ]
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3dd7b54..1dd719e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12875,7 +12875,7 @@
         Slogf.i(LOG_TAG, "Stopping user %d", userId);
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
+            switch (mInjector.getIActivityManager().stopUserWithCallback(userId, null)) {
                 case ActivityManager.USER_OP_SUCCESS:
                     return UserManager.USER_OPERATION_SUCCESS;
                 case ActivityManager.USER_OP_IS_CURRENT:
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0a7f49d..3c6b500 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,29 +108,50 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accounts.AccountManagerService;
 import com.android.server.adaptiveauth.AdaptiveAuthService;
+import com.android.server.adb.AdbService;
+import com.android.server.alarm.AlarmManagerService;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.ambientcontext.AmbientContextManagerService;
+import com.android.server.app.GameManagerService;
 import com.android.server.appbinding.AppBindingService;
+import com.android.server.apphibernation.AppHibernationService;
 import com.android.server.appop.AppOpMigrationHelper;
 import com.android.server.appop.AppOpMigrationHelperImpl;
+import com.android.server.appprediction.AppPredictionManagerService;
+import com.android.server.appwidget.AppWidgetService;
 import com.android.server.art.ArtModuleServiceInitializer;
 import com.android.server.art.DexUseManagerLocal;
 import com.android.server.attention.AttentionManagerService;
 import com.android.server.audio.AudioService;
+import com.android.server.autofill.AutofillManagerService;
+import com.android.server.backup.BackupManagerService;
 import com.android.server.biometrics.AuthService;
 import com.android.server.biometrics.BiometricService;
 import com.android.server.biometrics.sensors.face.FaceService;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
 import com.android.server.biometrics.sensors.iris.IrisService;
+import com.android.server.blob.BlobStoreManagerService;
 import com.android.server.broadcastradio.BroadcastRadioService;
 import com.android.server.camera.CameraServiceProxy;
 import com.android.server.clipboard.ClipboardService;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.virtual.VirtualDeviceManagerService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.compat.PlatformCompatNative;
+import com.android.server.compat.overrides.AppCompatOverridesService;
+import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.PacProxyService;
+import com.android.server.content.ContentService;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.contentcapture.ContentCaptureManagerService;
+import com.android.server.contentsuggestions.ContentSuggestionsManagerService;
+import com.android.server.contextualsearch.ContextualSearchManagerService;
 import com.android.server.coverage.CoverageService;
 import com.android.server.cpu.CpuMonitorService;
+import com.android.server.credentials.CredentialManagerService;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.devicestate.DeviceStateManagerService;
@@ -147,14 +168,20 @@
 import com.android.server.input.InputManagerService;
 import com.android.server.inputmethod.InputMethodManagerService;
 import com.android.server.integrity.AppIntegrityManagerService;
+import com.android.server.job.JobSchedulerService;
 import com.android.server.lights.LightsService;
 import com.android.server.locales.LocaleManagerService;
 import com.android.server.location.LocationManagerService;
 import com.android.server.location.altitude.AltitudeService;
+import com.android.server.locksettings.LockSettingsService;
 import com.android.server.logcat.LogcatManagerService;
+import com.android.server.media.MediaResourceMonitorService;
 import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaSessionService;
 import com.android.server.media.metrics.MediaMetricsManagerService;
 import com.android.server.media.projection.MediaProjectionManagerService;
+import com.android.server.midi.MidiService;
+import com.android.server.musicrecognition.MusicRecognitionManagerService;
 import com.android.server.net.NetworkManagementService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.watchlist.NetworkWatchlistService;
@@ -195,12 +222,16 @@
 import com.android.server.power.ThermalManagerService;
 import com.android.server.power.hint.HintManagerService;
 import com.android.server.powerstats.PowerStatsService;
+import com.android.server.print.PrintManagerService;
 import com.android.server.profcollect.ProfcollectForwardingService;
 import com.android.server.recoverysystem.RecoverySystemService;
 import com.android.server.resources.ResourcesManagerService;
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.role.RoleServicePlatformHelper;
+import com.android.server.rollback.RollbackManagerService;
 import com.android.server.rotationresolver.RotationResolverManagerService;
+import com.android.server.search.SearchManagerService;
+import com.android.server.searchui.SearchUiManagerService;
 import com.android.server.security.AttestationVerificationManagerService;
 import com.android.server.security.FileIntegrityService;
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
@@ -210,16 +241,28 @@
 import com.android.server.sensorprivacy.SensorPrivacyService;
 import com.android.server.sensors.SensorService;
 import com.android.server.signedconfig.SignedConfigService;
+import com.android.server.slice.SliceManagerService;
+import com.android.server.smartspace.SmartspaceManagerService;
 import com.android.server.soundtrigger.SoundTriggerService;
 import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
+import com.android.server.speech.SpeechRecognitionManagerService;
+import com.android.server.stats.bootstrap.StatsBootstrapAtomService;
+import com.android.server.stats.pull.StatsPullAtomService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.systemcaptions.SystemCaptionsManagerService;
 import com.android.server.telecom.TelecomLoaderService;
 import com.android.server.testharness.TestHarnessModeService;
 import com.android.server.textclassifier.TextClassificationManagerService;
 import com.android.server.textservices.TextServicesManagerService;
+import com.android.server.texttospeech.TextToSpeechManagerService;
+import com.android.server.timedetector.GnssTimeUpdateService;
 import com.android.server.timedetector.NetworkTimeUpdateService;
+import com.android.server.timedetector.TimeDetectorService;
+import com.android.server.timezonedetector.TimeZoneDetectorService;
+import com.android.server.timezonedetector.location.LocationTimeZoneManagerService;
 import com.android.server.tracing.TracingServiceProxy;
+import com.android.server.translation.TranslationManagerService;
 import com.android.server.trust.TrustManagerService;
 import com.android.server.tv.TvInputManagerService;
 import com.android.server.tv.TvRemoteService;
@@ -227,10 +270,15 @@
 import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService;
 import com.android.server.twilight.TwilightService;
 import com.android.server.uri.UriGrantsManagerService;
+import com.android.server.usage.StorageStatsService;
 import com.android.server.usage.UsageStatsService;
+import com.android.server.usb.UsbService;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.vibrator.VibratorManagerService;
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
 import com.android.server.vr.VrManagerService;
+import com.android.server.wallpaper.WallpaperManagerService;
+import com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService;
 import com.android.server.wearable.WearableSensingManagerService;
 import com.android.server.webkit.WebViewUpdateService;
 import com.android.server.wm.ActivityTaskManagerService;
@@ -268,44 +316,20 @@
      * Implementation class names. TODO: Move them to a codegen class or load
      * them from the build system somehow.
      */
-    private static final String BACKUP_MANAGER_SERVICE_CLASS =
-            "com.android.server.backup.BackupManagerService$Lifecycle";
-    private static final String APPWIDGET_SERVICE_CLASS =
-            "com.android.server.appwidget.AppWidgetService";
     private static final String ARC_NETWORK_SERVICE_CLASS =
             "com.android.server.arc.net.ArcNetworkService";
     private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
             "com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
     private static final String ARC_SYSTEM_HEALTH_SERVICE =
             "com.android.server.arc.health.ArcSystemHealthService";
-    private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
-            "com.android.server.voiceinteraction.VoiceInteractionManagerService";
-    private static final String APP_HIBERNATION_SERVICE_CLASS =
-            "com.android.server.apphibernation.AppHibernationService";
-    private static final String PRINT_MANAGER_SERVICE_CLASS =
-            "com.android.server.print.PrintManagerService";
-    private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
-            "com.android.server.companion.CompanionDeviceManagerService";
-    private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS =
-            "com.android.server.companion.virtual.VirtualDeviceManagerService";
     private static final String STATS_COMPANION_APEX_PATH =
             "/apex/com.android.os.statsd/javalib/service-statsd.jar";
+    private static final String STATS_COMPANION_LIFECYCLE_CLASS =
+            "com.android.server.stats.StatsCompanion$Lifecycle";
     private static final String SCHEDULING_APEX_PATH =
             "/apex/com.android.scheduling/javalib/service-scheduling.jar";
     private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
             "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
-    private static final String CONNECTIVITY_SERVICE_APEX_PATH =
-            "/apex/com.android.tethering/javalib/service-connectivity.jar";
-    private static final String STATS_COMPANION_LIFECYCLE_CLASS =
-            "com.android.server.stats.StatsCompanion$Lifecycle";
-    private static final String STATS_PULL_ATOM_SERVICE_CLASS =
-            "com.android.server.stats.pull.StatsPullAtomService";
-    private static final String STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS =
-            "com.android.server.stats.bootstrap.StatsBootstrapAtomService$Lifecycle";
-    private static final String USB_SERVICE_CLASS =
-            "com.android.server.usb.UsbService$Lifecycle";
-    private static final String MIDI_SERVICE_CLASS =
-            "com.android.server.midi.MidiService$Lifecycle";
     private static final String WIFI_APEX_SERVICE_JAR_PATH =
             "/apex/com.android.wifi/javalib/service-wifi.jar";
     private static final String WIFI_SERVICE_CLASS =
@@ -320,16 +344,6 @@
             "com.android.server.wifi.p2p.WifiP2pService";
     private static final String LOWPAN_SERVICE_CLASS =
             "com.android.server.lowpan.LowpanService";
-    private static final String JOB_SCHEDULER_SERVICE_CLASS =
-            "com.android.server.job.JobSchedulerService";
-    private static final String LOCK_SETTINGS_SERVICE_CLASS =
-            "com.android.server.locksettings.LockSettingsService$Lifecycle";
-    private static final String STORAGE_MANAGER_SERVICE_CLASS =
-            "com.android.server.StorageManagerService$Lifecycle";
-    private static final String STORAGE_STATS_SERVICE_CLASS =
-            "com.android.server.usage.StorageStatsService$Lifecycle";
-    private static final String SEARCH_MANAGER_SERVICE_CLASS =
-            "com.android.server.search.SearchManagerService$Lifecycle";
     private static final String THERMAL_OBSERVER_CLASS =
             "com.android.clockwork.ThermalObserver";
     private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
@@ -354,91 +368,26 @@
             "com.android.clockwork.settings.WearSettingsService";
     private static final String WRIST_ORIENTATION_SERVICE_CLASS =
             "com.android.clockwork.wristorientation.WristOrientationService";
-    private static final String ACCOUNT_SERVICE_CLASS =
-            "com.android.server.accounts.AccountManagerService$Lifecycle";
-    private static final String CONTENT_SERVICE_CLASS =
-            "com.android.server.content.ContentService$Lifecycle";
-    private static final String WALLPAPER_SERVICE_CLASS =
-            "com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
-    private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
-            "com.android.server.autofill.AutofillManagerService";
-    private static final String CREDENTIAL_MANAGER_SERVICE_CLASS =
-            "com.android.server.credentials.CredentialManagerService";
-    private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
-            "com.android.server.contentcapture.ContentCaptureManagerService";
-    private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
-            "com.android.server.translation.TranslationManagerService";
-    private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
-            "com.android.server.musicrecognition.MusicRecognitionManagerService";
-    private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS =
-            "com.android.server.ambientcontext.AmbientContextManagerService";
-    private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
-            "com.android.server.systemcaptions.SystemCaptionsManagerService";
-    private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
-            "com.android.server.texttospeech.TextToSpeechManagerService";
+
     private static final String IOT_SERVICE_CLASS =
             "com.android.things.server.IoTSystemService";
-    private static final String SLICE_MANAGER_SERVICE_CLASS =
-            "com.android.server.slice.SliceManagerService$Lifecycle";
     private static final String CAR_SERVICE_HELPER_SERVICE_CLASS =
             "com.android.internal.car.CarServiceHelperService";
-    private static final String TIME_DETECTOR_SERVICE_CLASS =
-            "com.android.server.timedetector.TimeDetectorService$Lifecycle";
-    private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
-            "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
-    private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
-            "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle";
-    private static final String GNSS_TIME_UPDATE_SERVICE_CLASS =
-            "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle";
-    private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
-            "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
-    private static final String ADB_SERVICE_CLASS =
-            "com.android.server.adb.AdbService$Lifecycle";
-    private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
-            "com.android.server.speech.SpeechRecognitionManagerService";
-    private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
-            "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
-    private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
-            "com.android.server.appprediction.AppPredictionManagerService";
-    private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
-            "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
-    private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
-            "com.android.server.searchui.SearchUiManagerService";
-    private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
-            "com.android.server.smartspace.SmartspaceManagerService";
-    private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS =
-            "com.android.server.contextualsearch.ContextualSearchManagerService";
-    private static final String DEVICE_IDLE_CONTROLLER_CLASS =
-            "com.android.server.DeviceIdleController";
-    private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
-            "com.android.server.blob.BlobStoreManagerService";
     private static final String APPSEARCH_MODULE_LIFECYCLE_CLASS =
             "com.android.server.appsearch.AppSearchModule$Lifecycle";
     private static final String ISOLATED_COMPILATION_SERVICE_CLASS =
             "com.android.server.compos.IsolatedCompilationService";
-    private static final String ROLLBACK_MANAGER_SERVICE_CLASS =
-            "com.android.server.rollback.RollbackManagerService";
-    private static final String ALARM_MANAGER_SERVICE_CLASS =
-            "com.android.server.alarm.AlarmManagerService";
-    private static final String MEDIA_SESSION_SERVICE_CLASS =
-            "com.android.server.media.MediaSessionService";
-    private static final String MEDIA_RESOURCE_MONITOR_SERVICE_CLASS =
-            "com.android.server.media.MediaResourceMonitorService";
+    private static final String CONNECTIVITY_SERVICE_APEX_PATH =
+            "/apex/com.android.tethering/javalib/service-connectivity.jar";
     private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
             "com.android.server.ConnectivityServiceInitializer";
     private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS =
             "com.android.server.NetworkStatsServiceInitializer";
-    private static final String IP_CONNECTIVITY_METRICS_CLASS =
-            "com.android.server.connectivity.IpConnectivityMetrics";
     private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
             "com.android.server.media.MediaCommunicationService";
-    private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
-            "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
     private static final String HEALTHCONNECT_MANAGER_SERVICE_CLASS =
             "com.android.server.healthconnect.HealthConnectManagerService";
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
-    private static final String GAME_MANAGER_SERVICE_CLASS =
-            "com.android.server.app.GameManagerService$Lifecycle";
     private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
             "com.android.ecm.EnhancedConfirmationService";
 
@@ -461,6 +410,7 @@
                     + "OnDevicePersonalizationSystemService$Lifecycle";
     private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
             "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+
     private static final String DEVICE_LOCK_SERVICE_CLASS =
             "com.android.server.devicelock.DeviceLockService";
     private static final String DEVICE_LOCK_APEX_PATH =
@@ -1435,7 +1385,7 @@
 
         // Manages apk rollbacks.
         t.traceBegin("StartRollbackManagerService");
-        mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
+        mSystemServiceManager.startService(RollbackManagerService.class);
         t.traceEnd();
 
         // Tracks native tombstones.
@@ -1580,11 +1530,11 @@
 
             // The AccountManager must come before the ContentService
             t.traceBegin("StartAccountManagerService");
-            mSystemServiceManager.startService(ACCOUNT_SERVICE_CLASS);
+            mSystemServiceManager.startService(AccountManagerService.Lifecycle.class);
             t.traceEnd();
 
             t.traceBegin("StartContentService");
-            mSystemServiceManager.startService(CONTENT_SERVICE_CLASS);
+            mSystemServiceManager.startService(ContentService.Lifecycle.class);
             t.traceEnd();
 
             t.traceBegin("InstallSystemProviders");
@@ -1639,7 +1589,7 @@
 
             // TODO(aml-jobscheduler): Think about how to do it properly.
             t.traceBegin("StartAlarmManagerService");
-            mSystemServiceManager.startService(ALARM_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(AlarmManagerService.class);
             t.traceEnd();
 
             t.traceBegin("StartInputManagerService");
@@ -1721,7 +1671,7 @@
             }
 
             t.traceBegin("IpConnectivityMetrics");
-            mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS);
+            mSystemServiceManager.startService(IpConnectivityMetrics.class);
             t.traceEnd();
 
             t.traceBegin("NetworkWatchlistService");
@@ -1796,7 +1746,7 @@
 
             t.traceBegin("StartAccessibilityManagerService");
             try {
-                mSystemServiceManager.startService(ACCESSIBILITY_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(AccessibilityManagerService.Lifecycle.class);
             } catch (Throwable e) {
                 reportWtf("starting Accessibility Manager", e);
             }
@@ -1819,7 +1769,7 @@
                      * NotificationManagerService is dependant on StorageManagerService,
                      * (for media / usb notifications) so we must start StorageManagerService first.
                      */
-                    mSystemServiceManager.startService(STORAGE_MANAGER_SERVICE_CLASS);
+                    mSystemServiceManager.startService(StorageManagerService.Lifecycle.class);
                     storageManager = IStorageManager.Stub.asInterface(
                             ServiceManager.getService("mount"));
                 } catch (Throwable e) {
@@ -1829,7 +1779,7 @@
 
                 t.traceBegin("StartStorageStatsService");
                 try {
-                    mSystemServiceManager.startService(STORAGE_STATS_SERVICE_CLASS);
+                    mSystemServiceManager.startService(StorageStatsService.Lifecycle.class);
                 } catch (Throwable e) {
                     reportWtf("starting StorageStatsService", e);
                 }
@@ -1860,7 +1810,7 @@
         t.traceEnd();
 
         t.traceBegin("StartAppHibernationService");
-        mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+        mSystemServiceManager.startService(AppHibernationService.class);
         t.traceEnd();
 
         t.traceBegin("ArtManagerLocal");
@@ -1892,7 +1842,7 @@
         } else {
             t.traceBegin("StartLockSettingsService");
             try {
-                mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+                mSystemServiceManager.startService(LockSettingsService.Lifecycle.class);
                 lockSettings = ILockSettings.Stub.asInterface(
                         ServiceManager.getService("lock_settings"));
             } catch (Throwable e) {
@@ -1925,7 +1875,7 @@
             }
 
             t.traceBegin("StartDeviceIdleController");
-            mSystemServiceManager.startService(DEVICE_IDLE_CONTROLLER_CLASS);
+            mSystemServiceManager.startService(DeviceIdleController.class);
             t.traceEnd();
 
             // Always start the Device Policy Manager, so that the API is compatible with
@@ -1948,7 +1898,7 @@
             if (deviceHasConfigString(context,
                     R.string.config_defaultMusicRecognitionService)) {
                 t.traceBegin("StartMusicRecognitionManagerService");
-                mSystemServiceManager.startService(MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(MusicRecognitionManagerService.class);
                 t.traceEnd();
             } else {
                 Slog.d(TAG,
@@ -1966,7 +1916,7 @@
             if (deviceHasConfigString(
                     context, R.string.config_defaultAmbientContextDetectionService)) {
                 t.traceBegin("StartAmbientContextService");
-                mSystemServiceManager.startService(AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(AmbientContextManagerService.class);
                 t.traceEnd();
             } else {
                 Slog.d(TAG, "AmbientContextManagerService not defined by OEM or disabled by flag");
@@ -1974,13 +1924,13 @@
 
             // System Speech Recognition Service
             t.traceBegin("StartSpeechRecognitionManagerService");
-            mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(SpeechRecognitionManagerService.class);
             t.traceEnd();
 
             // App prediction manager service
             if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
                 t.traceBegin("StartAppPredictionService");
-                mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(AppPredictionManagerService.class);
                 t.traceEnd();
             } else {
                 Slog.d(TAG, "AppPredictionService not defined by OEM");
@@ -1989,7 +1939,7 @@
             // Content suggestions manager service
             if (deviceHasConfigString(context, R.string.config_defaultContentSuggestionsService)) {
                 t.traceBegin("StartContentSuggestionsService");
-                mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+                mSystemServiceManager.startService(ContentSuggestionsManagerService.class);
                 t.traceEnd();
             } else {
                 Slog.d(TAG, "ContentSuggestionsService not defined by OEM");
@@ -1998,14 +1948,14 @@
             // Search UI manager service
             if (deviceHasConfigString(context, R.string.config_defaultSearchUiService)) {
                 t.traceBegin("StartSearchUiService");
-                mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(SearchUiManagerService.class);
                 t.traceEnd();
             }
 
             // Smartspace manager service
             if (deviceHasConfigString(context, R.string.config_defaultSmartspaceService)) {
                 t.traceBegin("StartSmartspaceService");
-                mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(SmartspaceManagerService.class);
                 t.traceEnd();
             } else {
                 Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
@@ -2015,7 +1965,7 @@
             if (deviceHasConfigString(context,
                     R.string.config_defaultContextualSearchPackageName)) {
                 t.traceBegin("StartContextualSearchService");
-                mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(ContextualSearchManagerService.class);
                 t.traceEnd();
             } else {
                 Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag");
@@ -2214,7 +2164,7 @@
 
             t.traceBegin("StartTimeDetectorService");
             try {
-                mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+                mSystemServiceManager.startService(TimeDetectorService.Lifecycle.class);
             } catch (Throwable e) {
                 reportWtf("starting TimeDetectorService service", e);
             }
@@ -2235,7 +2185,7 @@
 
             t.traceBegin("StartTimeZoneDetectorService");
             try {
-                mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
+                mSystemServiceManager.startService(TimeZoneDetectorService.Lifecycle.class);
             } catch (Throwable e) {
                 reportWtf("starting TimeZoneDetectorService service", e);
             }
@@ -2251,7 +2201,7 @@
 
             t.traceBegin("StartLocationTimeZoneManagerService");
             try {
-                mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(LocationTimeZoneManagerService.Lifecycle.class);
             } catch (Throwable e) {
                 reportWtf("starting LocationTimeZoneManagerService service", e);
             }
@@ -2260,7 +2210,7 @@
             if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) {
                 t.traceBegin("StartGnssTimeUpdateService");
                 try {
-                    mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS);
+                    mSystemServiceManager.startService(GnssTimeUpdateService.Lifecycle.class);
                 } catch (Throwable e) {
                     reportWtf("starting GnssTimeUpdateService service", e);
                 }
@@ -2270,7 +2220,7 @@
             if (!isWatch) {
                 t.traceBegin("StartSearchManagerService");
                 try {
-                    mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
+                    mSystemServiceManager.startService(SearchManagerService.Lifecycle.class);
                 } catch (Throwable e) {
                     reportWtf("starting Search Service", e);
                 }
@@ -2279,7 +2229,7 @@
 
             if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
                 t.traceBegin("StartWallpaperManagerService");
-                mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
+                mSystemServiceManager.startService(WallpaperManagerService.Lifecycle.class);
                 t.traceEnd();
             } else {
                 Slog.i(TAG, "Wallpaper service disabled by config");
@@ -2289,8 +2239,7 @@
             if (deviceHasConfigString(context,
                 R.string.config_defaultWallpaperEffectsGenerationService)) {
                 t.traceBegin("StartWallpaperEffectsGenerationService");
-                mSystemServiceManager.startService(
-                    WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(WallpaperEffectsGenerationManagerService.class);
                 t.traceEnd();
             }
 
@@ -2345,14 +2294,14 @@
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
                 // Start MIDI Manager service
                 t.traceBegin("StartMidiManager");
-                mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+                mSystemServiceManager.startService(MidiService.Lifecycle.class);
                 t.traceEnd();
             }
 
             // Start ADB Debugging Service
             t.traceBegin("StartAdbService");
             try {
-                mSystemServiceManager.startService(ADB_SERVICE_CLASS);
+                mSystemServiceManager.startService(AdbService.Lifecycle.class);
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting AdbService");
             }
@@ -2364,7 +2313,7 @@
                     || Build.IS_EMULATOR) {
                 // Manage USB host and device support
                 t.traceBegin("StartUsbService");
-                mSystemServiceManager.startService(USB_SERVICE_CLASS);
+                mSystemServiceManager.startService(UsbService.Lifecycle.class);
                 t.traceEnd();
             }
 
@@ -2396,7 +2345,7 @@
 
             // TODO(aml-jobscheduler): Think about how to do it properly.
             t.traceBegin("StartJobScheduler");
-            mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);
+            mSystemServiceManager.startService(JobSchedulerService.class);
             t.traceEnd();
 
             t.traceBegin("StartSoundTrigger");
@@ -2409,14 +2358,14 @@
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
                 t.traceBegin("StartBackupManager");
-                mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(BackupManagerService.Lifecycle.class);
                 t.traceEnd();
             }
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
                     || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
                 t.traceBegin("StartAppWidgetService");
-                mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+                mSystemServiceManager.startService(AppWidgetService.class);
                 t.traceEnd();
             }
 
@@ -2425,7 +2374,7 @@
             // of initializing various settings.  It will internally modify its behavior
             // based on that feature.
             t.traceBegin("StartVoiceRecognitionManager");
-            mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(VoiceInteractionManagerService.class);
             t.traceEnd();
 
             if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
@@ -2486,7 +2435,7 @@
             }
 
             t.traceBegin(START_BLOB_STORE_SERVICE);
-            mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(BlobStoreManagerService.class);
             t.traceEnd();
 
             // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
@@ -2507,7 +2456,7 @@
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
                 t.traceBegin("StartPrintManager");
-                mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(PrintManagerService.class);
                 t.traceEnd();
             }
 
@@ -2517,13 +2466,13 @@
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) {
                 t.traceBegin("StartCompanionDeviceManager");
-                mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(CompanionDeviceManagerService.class);
                 t.traceEnd();
             }
 
             if (context.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) {
                 t.traceBegin("StartVirtualDeviceManager");
-                mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS);
+                mSystemServiceManager.startService(VirtualDeviceManagerService.class);
                 t.traceEnd();
             }
 
@@ -2532,7 +2481,7 @@
             t.traceEnd();
 
             t.traceBegin("StartMediaSessionService");
-            mSystemServiceManager.startService(MEDIA_SESSION_SERVICE_CLASS);
+            mSystemServiceManager.startService(MediaSessionService.class);
             t.traceEnd();
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -2563,7 +2512,7 @@
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
                 t.traceBegin("StartMediaResourceMonitor");
-                mSystemServiceManager.startService(MEDIA_RESOURCE_MONITOR_SERVICE_CLASS);
+                mSystemServiceManager.startService(MediaResourceMonitorService.class);
                 t.traceEnd();
             }
 
@@ -2735,7 +2684,7 @@
 
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
             t.traceBegin("StartSliceManagerService");
-            mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(SliceManagerService.Lifecycle.class);
             t.traceEnd();
         }
 
@@ -2759,12 +2708,12 @@
 
         // Statsd pulled atoms
         t.traceBegin("StartStatsPullAtomService");
-        mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
+        mSystemServiceManager.startService(StatsPullAtomService.class);
         t.traceEnd();
 
         // Log atoms to statsd from bootstrap processes.
         t.traceBegin("StatsBootstrapAtomService");
-        mSystemServiceManager.startService(STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS);
+        mSystemServiceManager.startService(StatsBootstrapAtomService.Lifecycle.class);
         t.traceEnd();
 
         // Incidentd and dumpstated helper
@@ -2811,7 +2760,7 @@
 
         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) {
             t.traceBegin("StartAutoFillService");
-            mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(AutofillManagerService.class);
             t.traceEnd();
         }
 
@@ -2820,13 +2769,12 @@
                     DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
                     CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
             if (credentialManagerEnabled) {
-                if(isWatch &&
-                  !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
-                  Slog.d(TAG, "CredentialManager disabled on wear.");
+                if (isWatch && !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+                    Slog.d(TAG, "CredentialManager disabled on wear.");
                 } else {
-                  t.traceBegin("StartCredentialManagerService");
-                  mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
-                  t.traceEnd();
+                    t.traceBegin("StartCredentialManagerService");
+                    mSystemServiceManager.startService(CredentialManagerService.class);
+                    t.traceEnd();
                 }
             } else {
                 Slog.d(TAG, "CredentialManager disabled.");
@@ -2836,7 +2784,7 @@
         // Translation manager service
         if (deviceHasConfigString(context, R.string.config_defaultTranslationService)) {
             t.traceBegin("StartTranslationManagerService");
-            mSystemServiceManager.startService(TRANSLATION_MANAGER_SERVICE_CLASS);
+            mSystemServiceManager.startService(TranslationManagerService.class);
             t.traceEnd();
         } else {
             Slog.d(TAG, "TranslationService not defined by OEM");
@@ -2980,7 +2928,7 @@
         t.traceEnd();
 
         t.traceBegin("GameManagerService");
-        mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
+        mSystemServiceManager.startService(GameManagerService.Lifecycle.class);
         t.traceEnd();
 
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
@@ -3012,7 +2960,7 @@
         t.traceEnd();
 
         t.traceBegin("AppCompatOverridesService");
-        mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
+        mSystemServiceManager.startService(AppCompatOverridesService.Lifecycle.class);
         t.traceEnd();
 
         t.traceBegin("HealthConnectManagerService");
@@ -3397,14 +3345,14 @@
         }
 
         t.traceBegin("StartSystemCaptionsManagerService");
-        mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS);
+        mSystemServiceManager.startService(SystemCaptionsManagerService.class);
         t.traceEnd();
     }
 
     private void startTextToSpeechManagerService(@NonNull Context context,
             @NonNull TimingsTraceAndSlog t) {
         t.traceBegin("StartTextToSpeechManagerService");
-        mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+        mSystemServiceManager.startService(TextToSpeechManagerService.class);
         t.traceEnd();
     }
 
@@ -3439,7 +3387,7 @@
         }
 
         t.traceBegin("StartContentCaptureService");
-        mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS);
+        mSystemServiceManager.startService(ContentCaptureManagerService.class);
 
         ContentCaptureManagerInternal ccmi =
                 LocalServices.getService(ContentCaptureManagerInternal.class);
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 854bc0f..4b578af 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -1,5 +1,4 @@
 package: "android.server"
-container: "system"
 
 flag {
      namespace: "system_performance"
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 11893e7..44ed3df 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1601,7 +1601,7 @@
         ) {
             with(policy) { getPermissionFlags(appId, userId, permissionName) }
         } else {
-            if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+            if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
                 Slog.i(
                     LOG_TAG,
                     "$permissionName is not device aware permission, " +
@@ -1626,7 +1626,7 @@
         ) {
             with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
         } else {
-            if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+            if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
                 Slog.i(
                     LOG_TAG,
                     "$permissionName is not device aware permission, " +
@@ -2847,15 +2847,6 @@
                 PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
                 PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
 
-        /** These permissions are supported for virtual devices. */
-        // TODO: b/298661870 - Use new API to get the list of device aware permissions.
-        val DEVICE_AWARE_PERMISSIONS =
-            if (Flags.deviceAwarePermissionsEnabled()) {
-                setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
-            } else {
-                emptySet<String>()
-            }
-
         fun getFullerPermission(permissionName: String): String? =
             FULLER_PERMISSIONS[permissionName]
     }
diff --git a/services/proguard.flags b/services/proguard.flags
index a01e7dc..f84eff7 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -33,12 +33,6 @@
 -keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {
   public <methods>;
 }
--keep,allowoptimization,allowaccessmodification class * extends com.android.server.devicepolicy.BaseIDevicePolicyManager {
-  public <init>(...);
-}
--keep,allowoptimization,allowaccessmodification class com.android.server.wallpaper.WallpaperManagerService {
-  public <init>(...);
-}
 
 # Accessed from com.android.compos APEX
 -keep,allowoptimization,allowaccessmodification class com.android.internal.art.ArtStatsLog {
@@ -68,13 +62,6 @@
 -keep public class android.hidl.manager.** { *; }
 -keep public class com.android.server.wm.WindowManagerInternal { *; }
 
-# Notification extractors
-# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/res/values/config.xml.
--keep,allowoptimization,allowaccessmodification public class com.android.server.notification.** implements com.android.server.notification.NotificationSignalExtractor
-
-# OEM provided DisplayAreaPolicy.Provider defined in frameworks/base/core/res/res/values/config.xml.
--keep,allowoptimization,allowaccessmodification class com.android.server.wm.** implements com.android.server.wm.DisplayAreaPolicy$Provider
-
 # JNI keep rules
 # The global keep rule for native methods allows stripping of such methods if they're unreferenced
 # in Java. However, because system_server explicitly registers these methods from native code,
@@ -118,9 +105,6 @@
 
 # Miscellaneous reflection keep rules
 # TODO(b/210510433): Revisit and fix with @Keep.
--keep,allowoptimization,allowaccessmodification class com.android.server.usage.AppStandbyController {
-  public <init>(...);
-}
 -keep,allowoptimization,allowaccessmodification class android.hardware.usb.gadget.** { *; }
 
 # Needed when optimizations enabled
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index ad4d91f..6d89e80 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -27,6 +27,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Objects;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DisplayBrightnessStateTest {
@@ -101,7 +103,9 @@
                 .append("\n    customAnimationRate:")
                 .append(displayBrightnessState.getCustomAnimationRate())
                 .append("\n    shouldUpdateScreenBrightnessSetting:")
-                .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting());
+                .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting())
+                .append("\n    mBrightnessEvent:")
+                .append(Objects.toString(displayBrightnessState.getBrightnessEvent(), "null"));
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index b80d44f..5897d76 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -924,7 +924,7 @@
                 getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
 
         assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
-        assertEquals(0.0001f, mDisplayDeviceConfig.getBacklightFromBrightness(0.1f), ZERO_DELTA);
+        assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA);
         assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
     }
 
@@ -1649,18 +1649,28 @@
     private String evenDimmerConfig(boolean enabled) {
         return (enabled ? "<evenDimmer enabled=\"true\">" : "<evenDimmer enabled=\"false\">")
                 + "  <transitionPoint>0.1</transitionPoint>\n"
-                + "  <nits>0.2</nits>\n"
-                + "  <nits>2.0</nits>\n"
-                + "  <nits>500.0</nits>\n"
-                + "  <nits>1000.0</nits>\n"
-                + "  <backlight>0</backlight>\n"
-                + "  <backlight>0.0001</backlight>\n"
-                + "  <backlight>0.5</backlight>\n"
-                + "  <backlight>1.0</backlight>\n"
-                + "  <brightness>0</brightness>\n"
-                + "  <brightness>0.1</brightness>\n"
-                + "  <brightness>0.5</brightness>\n"
-                + "  <brightness>1.0</brightness>\n"
+                + "  <brightnessMapping>\n"
+                + "      <brightnessPoint>\n"
+                + "        <nits>0.2</nits>\n"
+                + "        <backlight>0</backlight>\n"
+                + "        <brightness>0</brightness>\n"
+                + "      </brightnessPoint>\n"
+                + "      <brightnessPoint>\n"
+                + "        <nits>2.0</nits>\n"
+                + "        <backlight>0.01</backlight>\n"
+                + "        <brightness>0.002</brightness>\n"
+                + "      </brightnessPoint>\n"
+                + "      <brightnessPoint>\n"
+                + "        <nits>500.0</nits>\n"
+                + "        <backlight>0.5</backlight>\n"
+                + "        <brightness>0.5</brightness>\n"
+                + "      </brightnessPoint>\n"
+                + "      <brightnessPoint>\n"
+                + "        <nits>1000</nits>\n"
+                + "        <backlight>1.0</backlight>\n"
+                + "        <brightness>1.0</brightness>\n"
+                + "      </brightnessPoint>\n"
+                + "  </brightnessMapping>\n"
                 + " <luxToMinimumNitsMap>\n"
                 + "     <point>\n"
                 + "         <value>10</value> <nits>0.3</nits>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b7fa7ea..66dd43a1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -960,6 +960,9 @@
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
                 VIRTUAL_DISPLAY_NAME, width, height, dpi);
         builder.setUniqueId(uniqueId);
+        builder.setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
+        when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
         final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
                 mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
@@ -972,6 +975,7 @@
                 VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
         builder2.setUniqueId(uniqueId2);
         builder2.setDisplayIdToMirror(firstDisplayId);
+        builder2.setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
         final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
                 mMockAppToken2 /* callback */, null /* projection */,
                 PACKAGE_NAME);
@@ -2448,8 +2452,9 @@
         LogicalDisplay display =
                 logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
         assertThat(display.isEnabledLocked()).isFalse();
+        // TODO(b/332711269) make sure only one DISPLAY_GROUP_EVENT_ADDED sent.
         assertThat(callback.receivedEvents()).containsExactly(DISPLAY_GROUP_EVENT_ADDED,
-                EVENT_DISPLAY_CONNECTED).inOrder();
+                DISPLAY_GROUP_EVENT_ADDED, EVENT_DISPLAY_CONNECTED).inOrder();
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1a71e77..ea08be4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -317,7 +317,8 @@
                 mDisplayEventCaptor.capture());
         assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
         assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
-        verify(mMockedLogicalDisplay).setEnabledLocked(false);
+        verify(mMockedLogicalDisplayMapper).setDisplayEnabledLocked(eq(mMockedLogicalDisplay),
+                eq(false));
         clearInvocations(mMockedLogicalDisplayMapper);
         clearInvocations(mMockedLogicalDisplay);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 6ed8238..1ae4099 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,7 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -111,8 +111,7 @@
         DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
         int targetDisplayState = Display.STATE_DOZE;
         when(mDisplayBrightnessStrategySelector.selectStrategy(
-                eq(new StrategySelectionRequest(displayPowerRequest, targetDisplayState))))
-                .thenReturn(displayBrightnessStrategy);
+                any(StrategySelectionRequest.class))).thenReturn(displayBrightnessStrategy);
         mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
         verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
         assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
@@ -164,6 +163,7 @@
         // No brightness is set if the pending brightness is invalid
         mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
         assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        assertFalse(mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated());
 
         // user set brightness is not set if the current and the pending brightness are same.
         float currentBrightness = 0.4f;
@@ -175,6 +175,7 @@
         mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
         mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
         assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        assertFalse(mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated());
         verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
         assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
@@ -188,6 +189,7 @@
         mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
         mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
         assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        assertTrue(mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated());
         assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
                 pendingScreenBrightness, /* delta= */ 0.0f);
         assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 4c9dd58..b8858cc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -40,6 +41,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
 import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
@@ -80,6 +82,8 @@
     @Mock
     private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
     @Mock
+    private AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy2;
+    @Mock
     private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
     @Mock
     private Resources mResources;
@@ -126,12 +130,18 @@
                 }
 
                 @Override
-                AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context,
+                AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
                         int displayId) {
                     return mAutomaticBrightnessStrategy;
                 }
 
                 @Override
+                AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context,
+                        int displayId) {
+                    return mAutomaticBrightnessStrategy2;
+                }
+
+                @Override
                 OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
                     return mOffloadBrightnessStrategy;
                 }
@@ -162,7 +172,8 @@
         when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
                 DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
+                                0.1f, false)),
                 mDozeBrightnessModeStrategy);
     }
 
@@ -175,7 +186,8 @@
         when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
                 DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
         assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE)),
+                new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
+                        0.1f, false)),
                 mDozeBrightnessModeStrategy);
     }
 
@@ -184,7 +196,8 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
                 DisplayManagerInternal.DisplayPowerRequest.class);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF,
+                                0.1f, false)),
                 mScreenOffBrightnessModeStrategy);
     }
 
@@ -195,7 +208,8 @@
         displayPowerRequest.screenBrightnessOverride = 0.4f;
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
                 mOverrideBrightnessStrategy);
     }
 
@@ -207,7 +221,8 @@
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
                 mTemporaryBrightnessStrategy);
     }
 
@@ -220,7 +235,8 @@
         displayPowerRequest.screenBrightnessOverride = Float.NaN;
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
                 mBoostBrightnessStrategy);
     }
 
@@ -233,7 +249,8 @@
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
         when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
                 mInvalidBrightnessStrategy);
     }
 
@@ -243,7 +260,8 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
                 mFollowerBrightnessStrategy);
     }
 
@@ -257,14 +275,39 @@
         displayPowerRequest.screenBrightnessOverride = Float.NaN;
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
-        when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
+        when(mAutomaticBrightnessStrategy2.shouldUseAutoBrightness()).thenReturn(true);
         when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
                 mOffloadBrightnessStrategy);
     }
 
     @Test
+    public void selectStrategy_selectsAutomaticStrategyWhenValid() {
+        when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
+        when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
+                mAutomaticBrightnessStrategy);
+        verifyZeroInteractions(mOffloadBrightnessStrategy);
+        verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON,
+                false, BrightnessReason.REASON_UNKNOWN,
+                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f, false);
+
+    }
+
+    @Test
     public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() {
         when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false);
         mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
@@ -277,7 +320,8 @@
         when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
         assertNotEquals(mOffloadBrightnessStrategy,
                 mDisplayBrightnessStrategySelector.selectStrategy(
-                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)));
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)));
     }
 
     @Test
@@ -290,10 +334,13 @@
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
 
         mDisplayBrightnessStrategySelector.selectStrategy(
-                new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON));
+                new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                        0.1f, false));
 
         StrategySelectionNotifyRequest strategySelectionNotifyRequest =
-                new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy);
+                new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON,
+                        mFollowerBrightnessStrategy, 0.1f,
+                        false, false);
         verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
                 eq(strategySelectionNotifyRequest));
         verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
@@ -308,5 +355,22 @@
                 eq(strategySelectionNotifyRequest));
         verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
                 eq(strategySelectionNotifyRequest));
+        verify(mAutomaticBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+    }
+
+    @Test
+    public void getAutomaticBrightnessStrategy_getsAutomaticStrategy2IfRefactoringFlagIsNotSet() {
+        assertEquals(mAutomaticBrightnessStrategy2,
+                mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy());
+    }
+
+    @Test
+    public void getAutomaticBrightnessStrategy_getsAutomaticStrategyIfRefactoringFlagIsSet() {
+        when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        assertEquals(mAutomaticBrightnessStrategy,
+                mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy());
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
new file mode 100644
index 0000000..fd43720
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.After;
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategy2Test {
+    private static final int DISPLAY_ID = 0;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
+    private BrightnessConfiguration mBrightnessConfiguration;
+    private float mDefaultScreenAutoBrightnessAdjustment;
+    private Context mContext;
+    private AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy2(mContext, DISPLAY_ID);
+
+        mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                mAutomaticBrightnessController);
+        mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+                true);
+    }
+
+    @After
+    public void after() {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+    }
+
+    @Test
+    public void testAutoBrightnessState_AutoBrightnessDisabled() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, /* adjustment */ 0.5f,
+                        /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                        targetDisplayState, /* shouldResetShortTermModel */ true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsOff() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_OFF;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, /* adjustment */ 0.5f,
+                        /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                        targetDisplayState, /* shouldResetShortTermModel */ true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesNotAllow() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_DOZE;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, /* adjustment */ 0.5f,
+                        /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                        targetDisplayState, /* shouldResetShortTermModel */ true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_BrightnessReasonIsOverride() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, /* adjustment */ 0.5f,
+                        /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                        targetDisplayState, /* shouldResetShortTermModel */ true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_DOZE;
+        boolean allowAutoBrightnessWhileDozing = true;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, /* adjustment */ 0.4f,
+                        /* userChangedAutoBrightnessAdjustment= */ true, policy,
+                        targetDisplayState, /* shouldResetShortTermModel */ true);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsOn() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float pendingBrightnessAdjustment = 0.1f;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, pendingBrightnessAdjustment,
+                        /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        /* shouldResetShortTermModel */ true);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void accommodateUserBrightnessChangesWorksAsExpected() {
+        // Verify the state if automaticBrightnessController is configured.
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        boolean userSetBrightnessChanged = true;
+        float lastUserSetScreenBrightness = 0.2f;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        int targetDisplayState = Display.STATE_ON;
+        BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+        float temporaryAutoBrightnessAdjustments = 0.4f;
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
+                autoBrightnessState);
+        verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+                brightnessConfiguration,
+                lastUserSetScreenBrightness,
+                userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+                /* userChangedAutoBrightnessAdjustment= */ false, policy, targetDisplayState,
+                /* shouldResetShortTermModel= */ true);
+        assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        // Verify the state when automaticBrightnessController is not configured
+        setTemporaryAutoBrightnessAdjustment(Float.NaN);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
+                autoBrightnessState);
+        assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+    }
+
+    @Test
+    public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+        float brightnessState = 0.3f;
+        float autoBrightnessAdjustment = 0.2f;
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+                autoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+        assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+        float invalidBrightness = -0.5f;
+        mAutomaticBrightnessStrategy
+                .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(0,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+    }
+
+    @Test
+    public void updatePendingAutoBrightnessAdjustments() {
+        // Verify the state when the pendingAutoBrightnessAdjustments are not present
+        setPendingAutoBrightnessAdjustment(Float.NaN);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+        float autoBrightnessAdjustment = 0.3f;
+        setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+        float pendingAutoBrightnessAdjustment = 0.2f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+        setTemporaryAutoBrightnessAdjustment(0.1f);
+        assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(pendingAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    @Test
+    public void setAutomaticBrightnessWorksAsExpected() {
+        float automaticScreenBrightness = 0.3f;
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(automaticScreenBrightness);
+        when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                any(BrightnessEvent.class)))
+                .thenReturn(automaticScreenBrightness);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(automaticScreenBrightness,
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+                        new BrightnessEvent(DISPLAY_ID)), 0.0f);
+        assertEquals(automaticScreenBrightness,
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                        new BrightnessEvent(DISPLAY_ID)), 0.0f);
+    }
+
+    @Test
+    public void shouldUseAutoBrightness() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+    }
+
+    @Test
+    public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+        float pendingAutoBrightnessAdjustments = 0.3f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+        assertEquals(pendingAutoBrightnessAdjustments,
+                mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+    }
+
+    @Test
+    public void setTemporaryAutoBrightnessAdjustment() {
+        float temporaryAutoBrightnessAdjustment = 0.3f;
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+        assertEquals(temporaryAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+    }
+
+    @Test
+    public void setAutoBrightnessApplied() {
+        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+    }
+
+    @Test
+    public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+        int newDisplayId = 1;
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy2(mContext, newDisplayId);
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+        assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+    }
+
+    private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+    }
+
+    private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 4e55270..6e163ca 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -398,6 +398,34 @@
                 0.0f);
     }
 
+    @Test
+    public void isAutoBrightnessValid_returnsFalseWhenAutoBrightnessIsDisabled() {
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
+    }
+
+    @Test
+    public void isAutoBrightnessValid_returnsFalseWhenBrightnessIsInvalid() {
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true,
+                BrightnessReason.REASON_UNKNOWN,
+                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f,
+                false);
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
+                .thenReturn(Float.NaN);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
+    }
+
+    @Test
+    public void isAutoBrightnessValid_returnsTrueWhenBrightnessIsValid() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true,
+                BrightnessReason.REASON_UNKNOWN,
+                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f,
+                false);
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
+                .thenReturn(0.2f);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
+    }
+
     private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index 0eb8639..dc5cb8d6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -3,3 +3,5 @@
 per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
 per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
 per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
+per-file *Storage* = file:/core/java/android/os/storage/OWNERS
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java
new file mode 100644
index 0000000..2e4b97e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open 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;
+
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.multiuser.Flags;
+import android.os.UserManager;
+import android.os.storage.ICeStorageLockEventListener;
+import android.os.storage.StorageManagerInternal;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class StorageManagerServiceTest {
+
+    private final Context mRealContext = androidx.test.platform.app.InstrumentationRegistry
+            .getInstrumentation().getTargetContext();
+    private StorageManagerService mStorageManagerService;
+    private StorageManagerInternal mStorageManagerInternal;
+
+    private static final int TEST_USER_ID = 1001;
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(UserManager.class)
+            .build();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private static class TestStorageEventListener implements ICeStorageLockEventListener {
+
+        private int mExpectedUserId;
+
+        private TestStorageEventListener(int userId) {
+            mExpectedUserId = userId;
+        }
+
+        @Override
+        public void onStorageLocked(int userId) {
+            assertThat(userId).isEqualTo(mExpectedUserId);
+        }
+    }
+
+
+    @Before
+    public void setFixtures() {
+        // Called when WatchedUserStates is constructed
+        doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
+
+        mStorageManagerService = new StorageManagerService(mRealContext);
+        mStorageManagerInternal = LocalServices.getService(StorageManagerInternal.class);
+        assertWithMessage("LocalServices.getService(StorageManagerInternal.class)")
+                .that(mStorageManagerInternal).isNotNull();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(StorageManagerInternal.class);
+    }
+
+    @Test
+    public void testRegisterLockEventListener() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        CopyOnWriteArrayList<ICeStorageLockEventListener> storageLockEventListeners =
+                mStorageManagerService.getCeStorageEventCallbacks();
+        assertThat(storageLockEventListeners).isNotNull();
+        int registeredCallbackCount = storageLockEventListeners.size();
+        TestStorageEventListener testStorageEventListener =
+                new TestStorageEventListener(TEST_USER_ID);
+        mStorageManagerInternal.registerStorageLockEventListener(testStorageEventListener);
+        assertNumberOfStorageCallbackReceivers(registeredCallbackCount + 1);
+
+        mStorageManagerInternal.unregisterStorageLockEventListener(testStorageEventListener);
+        assertNumberOfStorageCallbackReceivers(registeredCallbackCount);
+    }
+
+    @Test
+    public void testDispatchCeStorageLockEvent() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+
+        assertThat(mStorageManagerService.getCeStorageEventCallbacks()).isNotNull();
+        int callbackReceiverSize = mStorageManagerService.getCeStorageEventCallbacks().size();
+        TestStorageEventListener testStorageEventListener =
+                spy(new TestStorageEventListener(TEST_USER_ID));
+
+        // Add testStorageEventListener to the list of storage callback listeners
+        mStorageManagerService.getCeStorageEventCallbacks().add(testStorageEventListener);
+        assertNumberOfStorageCallbackReceivers(callbackReceiverSize + 1);
+
+        mStorageManagerService.dispatchCeStorageLockedEvent(TEST_USER_ID);
+        verify(testStorageEventListener).onStorageLocked(eq(TEST_USER_ID));
+
+        // Remove testStorageEventListener from the list of storage callback listeners
+        mStorageManagerService.getCeStorageEventCallbacks().remove(testStorageEventListener);
+        assertNumberOfStorageCallbackReceivers(callbackReceiverSize);
+    }
+
+    private void assertNumberOfStorageCallbackReceivers(int callbackReceiverSize) {
+        assertThat(mStorageManagerService.getCeStorageEventCallbacks()).isNotNull();
+        assertThat(mStorageManagerService.getCeStorageEventCallbacks().size())
+                    .isEqualTo(callbackReceiverSize);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index a7430e5..419bcb8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -38,6 +38,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
 import static com.android.server.am.ActivityManagerService.Injector;
@@ -692,6 +693,31 @@
         assertEquals(uid, -1);
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testFifoSwitch() {
+        addUidRecord(TEST_UID, TEST_PACKAGE);
+        final ProcessRecord fifoProc = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+        final var wpc = fifoProc.getWindowProcessController();
+        spyOn(wpc);
+        doReturn(true).when(wpc).useFifoUiScheduling();
+        fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+        assertTrue(fifoProc.useFifoUiScheduling());
+        assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+
+        // If there is a request to use more CPU resource (e.g. camera), the current fifo process
+        // should switch the capability of using fifo.
+        final UidRecord uidRecord = addUidRecord(TEST_UID + 1, TEST_PACKAGE + 1);
+        uidRecord.setCurProcState(PROCESS_STATE_TOP);
+        mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), false /* allowSpecifiedFifo */);
+        assertFalse(fifoProc.useFifoUiScheduling());
+        mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), true /* allowSpecifiedFifo */);
+        assertTrue(fifoProc.useFifoUiScheduling());
+
+        fifoProc.makeInactive(mAms.mProcessStats);
+        assertFalse(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+    }
+
     @Test
     public void testGlobalIsolatedUidAllocator() {
         final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids;
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index 18dc114..7e17909 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.annotation.NonNull;
 import android.app.backup.BackupHelper;
 import android.app.backup.BackupHelperWithLogger;
@@ -29,8 +31,6 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArraySet;
 
-import static org.mockito.Mockito.when;
-
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -92,7 +92,8 @@
                         "people",
                         "app_locales",
                         "app_gender",
-                        "companion");
+                        "companion",
+                        "system_gender");
     }
 
     @Test
@@ -116,7 +117,8 @@
                         "people",
                         "app_locales",
                         "app_gender",
-                        "companion");
+                        "companion",
+                        "system_gender");
     }
 
     @Test
@@ -132,7 +134,9 @@
                         "notifications",
                         "permissions",
                         "app_locales",
-                        "companion");
+                        "companion",
+                        "app_gender",
+                        "system_gender");
     }
 
     @Test
@@ -152,7 +156,9 @@
                         "account_manager",
                         "usage_stats",
                         "shortcut_manager",
-                        "companion");
+                        "companion",
+                        "app_gender",
+                        "system_gender");
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index b415682..0532e04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -55,6 +55,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -63,8 +64,7 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
-import android.security.Authorization;
-import android.security.authorization.IKeystoreAuthorization;
+import android.security.KeyStoreAuthorization;
 import android.service.trust.TrustAgentService;
 import android.testing.TestableContext;
 import android.view.IWindowManager;
@@ -96,7 +96,6 @@
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .spyStatic(ActivityManager.class)
-            .spyStatic(Authorization.class)
             .mockStatic(ServiceManager.class)
             .mockStatic(WindowManagerGlobal.class)
             .build();
@@ -126,14 +125,13 @@
     private @Mock DevicePolicyManager mDevicePolicyManager;
     private @Mock FaceManager mFaceManager;
     private @Mock FingerprintManager mFingerprintManager;
-    private @Mock IKeystoreAuthorization mKeystoreAuthorization;
+    private @Mock KeyStoreAuthorization mKeyStoreAuthorization;
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock PackageManager mPackageManager;
     private @Mock UserManager mUserManager;
     private @Mock IWindowManager mWindowManager;
 
     private HandlerThread mHandlerThread;
-    private TrustManagerService.Injector mInjector;
     private TrustManagerService mService;
     private ITrustManager mTrustManager;
 
@@ -145,8 +143,6 @@
         when(mFaceManager.getSensorProperties()).thenReturn(List.of());
         when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
 
-        doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
-
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
         when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
@@ -193,8 +189,7 @@
 
         mHandlerThread = new HandlerThread("handler");
         mHandlerThread.start();
-        mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
-        mService = new TrustManagerService(mMockContext, mInjector);
+        mService = new TrustManagerService(mMockContext, new MockInjector(mMockContext));
 
         // Get the ITrustManager from the new TrustManagerService.
         mService.onStart();
@@ -204,6 +199,27 @@
         mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
     }
 
+    private class MockInjector extends TrustManagerService.Injector {
+        MockInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        LockPatternUtils getLockPatternUtils() {
+            return mLockPatternUtils;
+        }
+
+        @Override
+        KeyStoreAuthorization getKeyStoreAuthorization() {
+            return mKeyStoreAuthorization;
+        }
+
+        @Override
+        Looper getLooper() {
+            return mHandlerThread.getLooper();
+        }
+    }
+
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(SystemServiceManager.class);
@@ -371,14 +387,14 @@
 
         when(mWindowManager.isKeyguardLocked()).thenReturn(false);
         mTrustManager.reportKeyguardShowingChanged();
-        verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
-        verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+        verify(mKeyStoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+        verify(mKeyStoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
 
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
         mTrustManager.reportKeyguardShowingChanged();
-        verify(mKeystoreAuthorization)
+        verify(mKeyStoreAuthorization)
                 .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
-        verify(mKeystoreAuthorization)
+        verify(mKeyStoreAuthorization)
                 .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
     }
 
@@ -392,10 +408,10 @@
         setupMocksForProfile(/* unifiedChallenge= */ false);
 
         mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
-        verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+        verify(mKeyStoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
 
         mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
-        verify(mKeystoreAuthorization)
+        verify(mKeyStoreAuthorization)
                 .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
     }
 
@@ -572,11 +588,11 @@
     private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
         when(mWindowManager.isKeyguardLocked()).thenReturn(false);
         mTrustManager.reportKeyguardShowingChanged();
-        verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+        verify(mKeyStoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
 
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
         mTrustManager.reportKeyguardShowingChanged();
-        verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+        verify(mKeyStoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
                 eq(expectedWeakUnlockEnabled));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 7ecc7fd..29f3720 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -235,12 +235,11 @@
         int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
         Point expectedCropSize = new Point(expectedWidth, 1000);
         for (int mode: ALL_MODES) {
-            assertThat(WallpaperCropper.getAdjustedCrop(
-                    crop, bitmapSize, displaySize, true, false, mode))
-                    .isEqualTo(leftOf(crop, expectedCropSize));
-            assertThat(WallpaperCropper.getAdjustedCrop(
-                    crop, bitmapSize, displaySize, true, true, mode))
-                    .isEqualTo(rightOf(crop, expectedCropSize));
+            for (boolean rtl: List.of(false, true)) {
+                assertThat(WallpaperCropper.getAdjustedCrop(
+                        crop, bitmapSize, displaySize, true, rtl, mode))
+                        .isEqualTo(centerOf(crop, expectedCropSize));
+            }
         }
     }
 
@@ -362,11 +361,13 @@
     }
 
     /**
-     * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
-     * no suggested crops are provided.
+     * Test that {@link WallpaperCropper#getCrop} uses the full image when no crops are provided.
+     * If the image has more width/height ratio than the screen, keep that width for parallax up
+     * to {@link WallpaperCropper#MAX_PARALLAX}. If the crop has less width/height ratio, remove the
+     * surplus height, on both sides to keep the wallpaper centered.
      */
     @Test
-    public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+    public void testGetCrop_noSuggestedCrops() {
         setUpWithDisplays(STANDARD_DISPLAY);
         Point bitmapSize = new Point(800, 1000);
         Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
@@ -374,9 +375,11 @@
 
         List<Point> displaySizes = List.of(
                 new Point(500, 1000),
+                new Point(200, 1000),
                 new Point(1000, 500));
         List<Point> expectedCropSizes = List.of(
-                new Point(500, 1000),
+                new Point(Math.min(800, (int) (500 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
+                new Point(Math.min(800, (int) (200 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
                 new Point(800, 400));
 
         for (int i = 0; i < displaySizes.size(); i++) {
@@ -450,7 +453,8 @@
     /**
      * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
      * crop only for the relative unfolded orientation, creates the folded crop at the center of the
-     * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+     * unfolded crop, by removing content on two sides to match the folded screen dimensions, and
+     * then adds some width for parallax.
      * <p>
      * To simplify, in this test case all crops have the same size as the display (no zoom)
      * and are at the center of the image.
@@ -468,6 +472,7 @@
             int unfoldedTwo = getRotatedOrientation(unfoldedOne);
             Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
             Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+            List<Rect> unfoldedCrops = List.of(unfoldedCropOne, unfoldedCropTwo);
             SparseArray<Rect> suggestedCrops = new SparseArray<>();
             suggestedCrops.put(unfoldedOne, unfoldedCropOne);
             suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
@@ -476,15 +481,28 @@
             int foldedTwo = getFoldedOrientation(unfoldedTwo);
             Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
             Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+            List<Point> foldedDisplays = List.of(foldedDisplayOne, foldedDisplayTwo);
 
             for (boolean rtl : List.of(false, true)) {
-                assertThat(mWallpaperCropper.getCrop(
-                        foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
-                        .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+                for (int i = 0; i < 2; i++) {
+                    Rect unfoldedCrop = unfoldedCrops.get(i);
+                    Point foldedDisplay = foldedDisplays.get(i);
+                    Rect expectedCrop = centerOf(unfoldedCrop, foldedDisplay);
+                    int maxParallax = (int) (WallpaperCropper.MAX_PARALLAX * unfoldedCrop.width());
 
-                assertThat(mWallpaperCropper.getCrop(
-                        foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
-                        .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+                    // the expected behaviour is that we add width for parallax until we reach
+                    // either MAX_PARALLAX or the edge of the crop for the unfolded screen.
+                    if (rtl) {
+                        expectedCrop.left = Math.max(
+                                unfoldedCrop.left, expectedCrop.left - maxParallax);
+                    } else {
+                        expectedCrop.right = Math.min(
+                                unfoldedCrop.right, unfoldedCrop.right + maxParallax);
+                    }
+                    assertThat(mWallpaperCropper.getCrop(
+                            foldedDisplay, bitmapSize, suggestedCrops, rtl))
+                            .isEqualTo(expectedCrop);
+                }
             }
         }
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index 05d8a00..c4561b1 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -87,7 +87,7 @@
 
         final boolean[] userStopped = new boolean[1];
         CountDownLatch stopUserLatch = new CountDownLatch(1);
-        mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
+        mIam.stopUserWithCallback(mTestUserId, new IStopUserCallback.Stub() {
             @Override
             public void userStopped(int userId) throws RemoteException {
                 userStopped[0] = true;
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e189098..ab36ba2 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.nullable;
 import static org.mockito.Mockito.times;
@@ -45,6 +46,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -57,6 +59,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
@@ -155,6 +158,8 @@
         mPackageInfo.applicationInfo = new ApplicationInfo();
         mPackageInfo.applicationInfo.privateFlags = ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
         when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
+        when(mMockPackageManager.getPackageInfoAsUser(
+                        anyString(), anyInt(), anyInt())).thenReturn(mPackageInfo);
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
         when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
         when(mMockContext.getSystemServiceName(AppOpsManager.class)).thenReturn(
@@ -3586,6 +3591,19 @@
         }
 
         @Override
+        public Resources getResources() {
+            Resources mockResources = mock(Resources.class);
+            // config_canRemoveFirstAccount = true
+            when(mockResources.getBoolean(anyInt())).thenReturn(true);
+            return mockResources;
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mock(ContentResolver.class);
+        }
+
+        @Override
         public void sendBroadcastAsUser(Intent intent, UserHandle user) {
             sendBroadcastAsUser(intent, user, null, null);
         }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0a2a855..7c0dbf4 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -888,7 +888,7 @@
         int userId = -1;
 
         assertThrows(IllegalArgumentException.class,
-                () -> mUserController.stopUser(userId, /* force= */ true,
+                () -> mUserController.stopUser(userId,
                         /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
                         /* keyEvictedCallback= */ null));
     }
@@ -897,7 +897,7 @@
     public void testStopUser_systemUser() {
         int userId = UserHandle.USER_SYSTEM;
 
-        int r = mUserController.stopUser(userId, /* force= */ true,
+        int r = mUserController.stopUser(userId,
                 /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
                 /* keyEvictedCallback= */ null);
 
@@ -909,7 +909,7 @@
         setUpUser(TEST_USER_ID1, /* flags= */ 0);
         mUserController.startUser(TEST_USER_ID1, USER_START_MODE_FOREGROUND);
 
-        int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+        int r = mUserController.stopUser(TEST_USER_ID1,
                 /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
                 /* keyEvictedCallback= */ null);
 
@@ -1338,7 +1338,7 @@
 
     private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
             KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
-        int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+        int r = mUserController.stopUser(userId, /* allowDelayedLocking= */
                 allowDelayedLocking, null, keyEvictedCallback);
         assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
         assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 74eb79d..34092b6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -68,7 +68,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
 
 import androidx.test.filters.SmallTest;
 
@@ -105,7 +105,7 @@
     @Mock private IBiometricServiceReceiver mClientReceiver;
     @Mock private IStatusBarService mStatusBarService;
     @Mock private IBiometricSysuiReceiver mSysuiReceiver;
-    @Mock private KeyStore mKeyStore;
+    @Mock private KeyStoreAuthorization mKeyStoreAuthorization;
     @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
     @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
     @Mock private BiometricCameraManager mBiometricCameraManager;
@@ -665,9 +665,10 @@
         final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
                 checkDevicePolicyManager);
         return new AuthSession(mContext, mBiometricContext, mStatusBarService, mSysuiReceiver,
-                mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId,
-                operationId, userId, mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
-                false /* debugEnabled */, mFingerprintSensorProps, mBiometricFrameworkStatsLogger);
+                mKeyStoreAuthorization, mRandom, mClientDeathReceiver, preAuthInfo, mToken,
+                requestId, operationId, userId, mSensorReceiver, mClientReceiver, TEST_PACKAGE,
+                promptInfo, false /* debugEnabled */, mFingerprintSensorProps,
+                mBiometricFrameworkStatsLogger);
     }
 
     private PromptInfo createPromptInfo(@Authenticators.Types int authenticators) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index a852677..5fd29c2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -82,8 +82,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.authorization.IKeystoreAuthorization;
+import android.security.KeyStoreAuthorization;
 import android.service.gatekeeper.IGateKeeperService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -182,7 +181,7 @@
     private BiometricHandlerProvider mBiometricHandlerProvider;
 
     @Mock
-    private IKeystoreAuthorization mKeystoreAuthService;
+    private KeyStoreAuthorization mKeyStoreAuthorization;
 
     @Mock
     private IGateKeeperService mGateKeeperService;
@@ -207,7 +206,7 @@
         when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
         when(mInjector.getSettingObserver(any(), any(), any()))
                 .thenReturn(mock(BiometricService.SettingObserver.class));
-        when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
+        when(mInjector.getKeyStoreAuthorization()).thenReturn(mock(KeyStoreAuthorization.class));
         when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
         when(mInjector.getBiometricStrengthController(any()))
                 .thenReturn(mock(BiometricStrengthController.class));
@@ -243,7 +242,7 @@
                 mStatusBarService, null /* handler */,
                 mAuthSessionCoordinator);
         when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
-        when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
+        when(mInjector.getKeyStoreAuthorization()).thenReturn(mKeyStoreAuthorization);
         when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
         when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
         when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
@@ -682,9 +681,9 @@
         waitForIdle();
         // HAT sent to keystore
         if (isStrongBiometric) {
-            verify(mBiometricService.mKeyStore).addAuthToken(AdditionalMatchers.aryEq(HAT));
+            verify(mKeyStoreAuthorization).addAuthToken(AdditionalMatchers.aryEq(HAT));
         } else {
-            verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+            verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
         }
         // Send onAuthenticated to client
         verify(mReceiver1).onAuthenticationSucceeded(
@@ -747,7 +746,7 @@
         waitForIdle();
         // Waiting for SystemUI to send confirmation callback
         assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState());
-        verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+        verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
 
         // SystemUI sends confirm, HAT is sent to keystore and client is notified.
         mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
@@ -755,9 +754,9 @@
                 null /* credentialAttestation */);
         waitForIdle();
         if (isStrongBiometric) {
-            verify(mBiometricService.mKeyStore).addAuthToken(AdditionalMatchers.aryEq(HAT));
+            verify(mKeyStoreAuthorization).addAuthToken(AdditionalMatchers.aryEq(HAT));
         } else {
-            verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+            verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
         }
         verify(mReceiver1).onAuthenticationSucceeded(
                 BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
@@ -1313,7 +1312,7 @@
                 eq(TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
                 eq(0 /* vendorCode */));
-        verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+        verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
         assertNull(mBiometricService.mAuthSession);
     }
 
@@ -1839,7 +1838,7 @@
 
         final long expectedResult = 31337L;
 
-        when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
+        when(mKeyStoreAuthorization.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
                 .thenReturn(expectedResult);
 
         mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
@@ -1848,7 +1847,8 @@
                 Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
 
         assertEquals(expectedResult, result);
-        verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators));
+        verify(mKeyStoreAuthorization).getLastAuthTime(eq(secureUserId),
+                eq(hardwareAuthenticators));
     }
 
     // Helper methods
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index 9f3f297..e4c56a7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -176,6 +176,21 @@
     }
 
     @Test
+    @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
+    public void registerAuthenticatorsLegacy_virtualFaceOnly() throws Exception {
+        initService();
+        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+                Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 1);
+
+        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+    }
+
+    @Test
     @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 20961a9..9a8cd48 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -223,6 +223,18 @@
     }
 
     @Test
+    public void registerAuthenticators_virtualFingerprintOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+                Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 1);
+
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+        waitForRegistration();
+
+        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+    }
+
+    @Test
     @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
         initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
diff --git a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
index 6c5a569..af6f6f2 100644
--- a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
+++ b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,6 +47,7 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.List;
 
@@ -67,7 +69,7 @@
     @Before
     public void setUp() throws Exception {
         mBackupHelper = new GrammaticalInflectionBackupHelper(
-                mGrammaticalInflectionService, mMockPackageManager);
+                null, mGrammaticalInflectionService, mMockPackageManager);
     }
 
     @Test
@@ -106,6 +108,28 @@
                 eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL));
     }
 
+    @Test
+    public void testSystemBackupPayload_returnsGender()
+            throws IOException, ClassNotFoundException {
+        doReturn(Configuration.GRAMMATICAL_GENDER_MASCULINE).when(mGrammaticalInflectionService)
+                .getSystemGrammaticalGender(any(), eq(DEFAULT_USER_ID));
+
+        int gender = convertByteArrayToInt(mBackupHelper.getSystemBackupPayload(DEFAULT_USER_ID));
+
+        assertEquals(gender, Configuration.GRAMMATICAL_GENDER_MASCULINE);
+    }
+
+    @Test
+    public void testApplySystemPayload_setSystemWideGrammaticalGender()
+            throws IOException {
+        mBackupHelper.applyRestoredSystemPayload(
+                intToByteArray(Configuration.GRAMMATICAL_GENDER_NEUTRAL), DEFAULT_USER_ID);
+
+        verify(mGrammaticalInflectionService).setSystemWideGrammaticalGender(
+                eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL),
+                eq(DEFAULT_USER_ID));
+    }
+
     private void mockAppInstalled() {
         ApplicationInfo dummyApp = new ApplicationInfo();
         dummyApp.packageName = DEFAULT_PACKAGE_NAME;
@@ -141,4 +165,15 @@
         }
         return data;
     }
+
+    private byte[] intToByteArray(final int gender) {
+        ByteBuffer bb = ByteBuffer.allocate(4);
+        bb.putInt(gender);
+        return bb.array();
+    }
+
+    private int convertByteArrayToInt(byte[] intBytes) {
+        ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
+        return byteBuffer.getInt();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index fa89278..44aa868 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -30,6 +30,9 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.system.Os;
 import android.text.FontConfig;
 import android.util.Xml;
@@ -38,8 +41,11 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.text.flags.Flags;
+
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
@@ -69,6 +75,9 @@
 
     private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     /**
      * A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files,
      * this test uses fake font files. A fake font file has its PostScript naem and revision as the
@@ -1097,6 +1106,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1116,6 +1126,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1);
@@ -1135,6 +1146,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1154,6 +1166,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1173,6 +1186,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1192,6 +1206,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1211,6 +1226,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1230,6 +1246,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1249,6 +1266,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1268,6 +1286,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1);
@@ -1287,6 +1306,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1306,6 +1326,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1325,6 +1346,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1344,6 +1366,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1363,6 +1386,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1382,6 +1406,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1401,6 +1426,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1420,6 +1446,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1439,6 +1466,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1458,6 +1486,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1477,6 +1506,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1497,6 +1527,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1517,6 +1548,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1537,6 +1569,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index 43bf537..72caae3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -242,7 +242,7 @@
     private void stopUser(int userId) throws RemoteException, InterruptedException {
         runWithLatch("stop user", countDownLatch -> {
             ActivityManager.getService()
-                    .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() {
+                    .stopUserWithCallback(userId, new IStopUserCallback.Stub() {
                         @Override
                         public void userStopped(int userId) {
                             countDownLatch.countDown();
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 52df010..eac9929 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -85,7 +85,6 @@
 import android.os.test.TestLooper;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
 import android.view.Display;
 import android.view.InputDevice;
 import android.view.KeyEvent;
@@ -743,15 +742,8 @@
 
     void assertSwitchKeyboardLayout(int direction, int displayId) {
         mTestLooper.dispatchAll();
-        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
-            verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
-                    eq(displayId), eq(mImeTargetWindowToken));
-            verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
-        } else {
-            verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
-            verify(mInputMethodManagerInternal, never())
-                    .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
-        }
+        verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+                eq(displayId), eq(mImeTargetWindowToken));
     }
 
     void assertTakeBugreport() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1355092..10eae57 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -46,6 +46,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
+import static android.server.wm.ActivityManagerTestBase.isTablet;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -75,6 +76,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -122,6 +124,7 @@
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
 import com.android.server.wm.utils.MockTracker;
+import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -1295,6 +1298,12 @@
      */
     @Test
     public void testDeliverIntentToTopActivityOfNonTopDisplay() {
+        // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with
+        //  desktop windowing mode
+        // Ignore test if desktop windowing is enabled on tablets as legacy multi-display
+        // behaviour will not be respected
+        assumeFalse(Flags.enableDesktopWindowingMode() && isTablet());
+
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
                 false /* mockGetRootTask */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 5aa4ba3e..695faa5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -504,22 +504,37 @@
         assertThat(balState.callerExplicitOptInOrOut()).isFalse();
         assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
         assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
-        assertThat(balState.toString()).isEqualTo(
-                "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
-                        + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
-                        + "false; callingUidProcState: NONEXISTENT; "
-                        + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
-                        + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
-                        + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
-                        + ".ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
-                        + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
-                        + "isCallForResult: false; isPendingIntent: false; autoOptInReason: "
-                        + "notPendingIntent; realCallingPackage: uid=1[debugOnly]; "
-                        + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
-                        + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
-                        + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
-                        + "originatingPendingIntent: null; realCallerApp: null; "
-                        + "balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: null]");
+        assertThat(balState.toString()).contains(
+                "[callingPackage: package.app1; "
+                        + "callingPackageTargetSdk: -1; "
+                        + "callingUid: 10001; "
+                        + "callingPid: 11001; "
+                        + "appSwitchState: 0; "
+                        + "callingUidHasAnyVisibleWindow: false; "
+                        + "callingUidProcState: NONEXISTENT; "
+                        + "isCallingUidPersistentSystemProcess: false; "
+                        + "forcedBalByPiSender: BSP.NONE; "
+                        + "intent: Intent { cmp=package.app3/someClass }; "
+                        + "callerApp: mCallerApp; "
+                        + "inVisibleTask: false; "
+                        + "balAllowedByPiCreator: BSP.ALLOW_BAL; "
+                        + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+                        + "resultIfPiCreatorAllowsBal: null; "
+                        + "hasRealCaller: true; "
+                        + "isCallForResult: false; "
+                        + "isPendingIntent: false; "
+                        + "autoOptInReason: notPendingIntent; "
+                        + "realCallingPackage: uid=1[debugOnly]; "
+                        + "realCallingPackageTargetSdk: -1; "
+                        + "realCallingUid: 1; "
+                        + "realCallingPid: 1; "
+                        + "realCallingUidHasAnyVisibleWindow: false; "
+                        + "realCallingUidProcState: NONEXISTENT; "
+                        + "isRealCallingUidPersistentSystemProcess: false; "
+                        + "originatingPendingIntent: null; "
+                        + "realCallerApp: null; "
+                        + "balAllowedByPiSender: BSP.ALLOW_BAL; "
+                        + "resultIfPiSenderAllowsBal: null");
     }
 
     @Test
@@ -588,21 +603,36 @@
         assertThat(balState.callerExplicitOptInOrOut()).isFalse();
         assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
         assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
-        assertThat(balState.toString()).isEqualTo(
-                "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
-                        + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
-                        + "false; callingUidProcState: NONEXISTENT; "
-                        + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
-                        + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
-                        + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
-                        + ".NONE; balAllowedByPiCreatorWithHardening: BSP.NONE; "
-                        + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
-                        + "isCallForResult: false; isPendingIntent: true; autoOptInReason: "
-                        + "null; realCallingPackage: uid=1[debugOnly]; "
-                        + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
-                        + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
-                        + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
-                        + "originatingPendingIntent: PendingIntentRecord; realCallerApp: null; "
-                        + "balAllowedByPiSender: BSP.ALLOW_FGS; resultIfPiSenderAllowsBal: null]");
+        assertThat(balState.toString()).contains(
+                "[callingPackage: package.app1; "
+                        + "callingPackageTargetSdk: -1; "
+                        + "callingUid: 10001; "
+                        + "callingPid: 11001; "
+                        + "appSwitchState: 0; "
+                        + "callingUidHasAnyVisibleWindow: false; "
+                        + "callingUidProcState: NONEXISTENT; "
+                        + "isCallingUidPersistentSystemProcess: false; "
+                        + "forcedBalByPiSender: BSP.NONE; "
+                        + "intent: Intent { cmp=package.app3/someClass }; "
+                        + "callerApp: mCallerApp; "
+                        + "inVisibleTask: false; "
+                        + "balAllowedByPiCreator: BSP.NONE; "
+                        + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+                        + "resultIfPiCreatorAllowsBal: null; "
+                        + "hasRealCaller: true; "
+                        + "isCallForResult: false; "
+                        + "isPendingIntent: true; "
+                        + "autoOptInReason: null; "
+                        + "realCallingPackage: uid=1[debugOnly]; "
+                        + "realCallingPackageTargetSdk: -1; "
+                        + "realCallingUid: 1; "
+                        + "realCallingPid: 1; "
+                        + "realCallingUidHasAnyVisibleWindow: false; "
+                        + "realCallingUidProcState: NONEXISTENT; "
+                        + "isRealCallingUidPersistentSystemProcess: false; "
+                        + "originatingPendingIntent: PendingIntentRecord; "
+                        + "realCallerApp: null; "
+                        + "balAllowedByPiSender: BSP.ALLOW_FGS; "
+                        + "resultIfPiSenderAllowsBal: null");
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index ae4faa8..9729c68 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -180,7 +180,8 @@
                 LocalServices.getService(ActivityManagerInternal.class));
         mAtmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
-        mWmInternal = LocalServices.getService(WindowManagerInternal.class);
+        mWmInternal = Objects.requireNonNull(
+                LocalServices.getService(WindowManagerInternal.class));
         mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
         LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
                 LegacyPermissionManagerInternal.class);
@@ -2737,12 +2738,8 @@
                     isManagedProfileVisible = true;
                 }
             }
-            final ScreenCapture.ScreenshotHardwareBuffer shb;
-            if (mWmInternal != null) {
-                shb = mWmInternal.takeAssistScreenshot();
-            } else {
-                shb = null;
-            }
+            final ScreenCapture.ScreenshotHardwareBuffer shb =
+                    mWmInternal.takeAssistScreenshot();
             final Bitmap bm = shb != null ? shb.asBitmap() : null;
             // Now that everything is fetched, putting it in the launchIntent.
             if (bm != null) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 10c17c1..7db4180 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9898,6 +9898,50 @@
      */
     public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
             "satellite_information_redirect_url_string";
+    /**
+     * Indicate whether a carrier supports emergency messaging. When this config is {@code  false},
+     * emergency call to satellite T911 handover will be disabled.
+     *
+     * This will need agreement with carriers before enabling this flag.
+     *
+     * The default value is false.
+     *
+     * @hide
+     */
+    public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
+            "emergency_messaging_supported_bool";
+
+    /**
+     * An integer key holds the timeout duration in milliseconds used to determine whether to hand
+     * over an emergency call to satellite T911.
+     *
+     * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
+     * and cellular service is not available. When the timer expires,
+     * {@link com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender} will send the
+     * event {@link TelephonyManager#EVENT_DISPLAY_EMERGENCY_MESSAGE} to Dialer, which will then
+     * prompt user to switch to using satellite emergency messaging.
+     *
+     * The default value is 30 seconds.
+     *
+     * @hide
+     */
+    public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
+            "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+
+    /**
+     * An int array that contains default capabilities for carrier enabled satellite roaming.
+     * If any PLMN is provided from the entitlement server, and it is not listed in
+     * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}, default capabilities
+     * will be used instead.
+     * <p>
+     * The default capabilities are
+     * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
+     * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
+     *
+     * @hide
+     */
+    public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
+            "carrier_roaming_satellite_default_services_int_array";
 
     /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -11045,7 +11089,15 @@
         sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
         sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
         sDefaults.putString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, "");
+        sDefaults.putIntArray(KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+                new int[] {
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_MMS
+                });
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+        sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
+        sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+                (int) TimeUnit.SECONDS.toMillis(30));
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c5f2d42..ba7ba532 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3137,7 +3137,7 @@
             if (useRootLocale) {
                 configurationKey.setLocale(Locale.ROOT);
             }
-            cacheKey = Pair.create(context.getPackageName(), configurationKey);
+            cacheKey = Pair.create(context.getPackageName() + ", subid=" + subId, configurationKey);
             synchronized (sResourcesCache) {
                 Resources cached = sResourcesCache.get(cacheKey);
                 if (cached != null) {
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 06fc3c6..579fda3 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -26,11 +26,13 @@
     /**
      * Called when satellite datagram send state changed.
      *
+     * @param datagramType The datagram type of currently being sent.
      * @param state The new send datagram transfer state.
      * @param sendPendingCount The number of datagrams that are currently being sent.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+    void onSendDatagramStateChanged(int datagramType, int state, int sendPendingCount,
+            int errorCode);
 
     /**
      * Called when satellite datagram receive state changed.
@@ -39,7 +41,7 @@
      * @param receivePendingCount The number of datagrams that are currently pending to be received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
+    void onReceiveDatagramStateChanged(int state, int receivePendingCount, int errorCode);
 
     /**
      * Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20b24b9..87bb0f0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -992,12 +992,19 @@
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
+    /**
+     * This type of datagram is used to keep the device in satellite connected state or check if
+     * there is any incoming message.
+     * @hide
+     */
+    public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
 
     /** @hide */
     @IntDef(prefix = "DATAGRAM_TYPE_", value = {
             DATAGRAM_TYPE_UNKNOWN,
             DATAGRAM_TYPE_SOS_MESSAGE,
-            DATAGRAM_TYPE_LOCATION_SHARING
+            DATAGRAM_TYPE_LOCATION_SHARING,
+            DATAGRAM_TYPE_KEEP_ALIVE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DatagramType {}
@@ -1077,8 +1084,13 @@
                             }
 
                             @Override
-                            public void onSendDatagramStateChanged(int state, int sendPendingCount,
-                                    int errorCode) {
+                            public void onSendDatagramStateChanged(int datagramType, int state,
+                                    int sendPendingCount, int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSendDatagramStateChanged(datagramType,
+                                                state, sendPendingCount, errorCode)));
+
+                                // For backward compatibility
                                 executor.execute(() -> Binder.withCleanCallingIdentity(
                                         () -> callback.onSendDatagramStateChanged(
                                                 state, sendPendingCount, errorCode)));
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index e020970..d8bd662 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -52,6 +52,19 @@
             @SatelliteManager.SatelliteResult int errorCode);
 
     /**
+     * Called when satellite datagram send state changed.
+     *
+     * @param datagramType The datagram type of currently being sent.
+     * @param state The new send datagram transfer state.
+     * @param sendPendingCount The number of datagrams that are currently being sent.
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     *
+     * @hide
+     */
+    void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+            @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+            @SatelliteManager.SatelliteResult int errorCode);
+    /**
      * Called when satellite datagram receive state changed.
      *
      * @param state The new receive datagram transfer state.
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
index 1e68c9d..9994826 100644
--- a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
@@ -19,10 +19,11 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_framework_cdm",
 }
 
 android_test {
-    name: "cdm_snippet",
+    name: "cdm_snippet_legacy",
     srcs: ["src/**/*.kt"],
     manifest: "AndroidManifest.xml",
 
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
index 03335c7..37cb850 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_framework_cdm",
 }
 
 python_test_host {
@@ -36,7 +37,7 @@
         tags: ["mobly"],
     },
     data: [
-        ":cdm_snippet",
+        ":cdm_snippet_legacy",
     ],
     version: {
         py2: {
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
index 9d1813f..7c7ef63 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
+++ b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
@@ -24,12 +24,12 @@
 
     <device name="device1">
         <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-            <option name="test-file-name" value="cdm_snippet.apk" />
+            <option name="test-file-name" value="cdm_snippet_legacy.apk" />
         </target_preparer>
     </device>
     <device name="device2">
         <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-            <option name="test-file-name" value="cdm_snippet.apk" />
+            <option name="test-file-name" value="cdm_snippet_legacy.apk" />
         </target_preparer>
     </device>
 
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index 511c948..1390218 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -17,8 +17,12 @@
 package com.android.server.wm.flicker.activityembedding.rotation
 
 import android.platform.test.annotations.Presubmit
+import android.tools.Position
+import android.tools.datatypes.Rect
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.Condition
+import android.tools.traces.DeviceStateDump
 import android.tools.traces.component.ComponentNameMatcher
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.setRotation
@@ -30,7 +34,14 @@
     override val transition: FlickerBuilder.() -> Unit = {
         setup { this.setRotation(flicker.scenario.startRotation) }
         teardown { testApp.exit(wmHelper) }
-        transitions { this.setRotation(flicker.scenario.endRotation) }
+        transitions {
+            this.setRotation(flicker.scenario.endRotation)
+            if (!flicker.scenario.isTablet) {
+                wmHelper.StateSyncBuilder()
+                    .add(navBarInPosition(flicker.scenario.isGesturalNavigation))
+                    .waitForAndVerify()
+            }
+        }
     }
 
     /** {@inheritDoc} */
@@ -76,4 +87,37 @@
         appLayerRotates_StartingPos()
         appLayerRotates_EndingPos()
     }
+
+    private fun navBarInPosition(isGesturalNavigation: Boolean): Condition<DeviceStateDump> {
+        return Condition("navBarPosition") { dump ->
+            val display =
+                dump.layerState.displays.filterNot { it.isOff }.minByOrNull { it.id }
+                    ?: error("There is no display!")
+            val displayArea = display.layerStackSpace
+            val navBarPosition = display.navBarPosition(isGesturalNavigation)
+            val navBarRegion = dump.layerState
+                .getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)
+                ?.visibleRegion?.bounds ?: Rect.EMPTY
+
+            when (navBarPosition) {
+                Position.TOP ->
+                    navBarRegion.top == displayArea.top &&
+                        navBarRegion.left == displayArea.left &&
+                        navBarRegion.right == displayArea.right
+                Position.BOTTOM ->
+                    navBarRegion.bottom == displayArea.bottom &&
+                        navBarRegion.left == displayArea.left &&
+                        navBarRegion.right == displayArea.right
+                Position.LEFT ->
+                    navBarRegion.left == displayArea.left &&
+                        navBarRegion.top == displayArea.top &&
+                        navBarRegion.bottom == displayArea.bottom
+                Position.RIGHT ->
+                    navBarRegion.right == displayArea.right &&
+                        navBarRegion.top == displayArea.top &&
+                        navBarRegion.bottom == displayArea.bottom
+                else -> error("Unknown position $navBarPosition")
+            }
+        }
+    }
 }
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index 8e210d4..f1df8a6 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -123,7 +123,9 @@
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
-    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+    @FlakyTest(bugId = 227143265)
+    @Test
+    override fun entireScreenCovered() = super.entireScreenCovered()
 
     @FlakyTest(bugId = 278227468)
     @Test
diff --git a/tests/FlickerTests/README.md b/tests/FlickerTests/README.md
index 6b28fdf..7429250 100644
--- a/tests/FlickerTests/README.md
+++ b/tests/FlickerTests/README.md
@@ -7,82 +7,17 @@
 
 ## Adding a Test
 
-By default tests should inherit from `RotationTestBase` or `NonRotationTestBase` and must override the variable `transitionToRun` (Kotlin) or the function `getTransitionToRun()` (Java).
-Only tests that are not supported by these classes should inherit directly from the `FlickerTestBase` class.
+By default, tests should inherit from `TestBase` and override the variable `transition` (Kotlin) or the function `getTransition()` (Java).
 
-### Rotation animations and transitions
+Inheriting from this class ensures the common assertions will be executed, namely:
 
-Tests that rotate the device should inherit from `RotationTestBase`.
-Tests that inherit from the class automatically receive start and end rotation values.  
-Moreover, these tests inherit the following checks:
 * all regions on the screen are covered
 * status bar is always visible
-* status bar rotates
+* status bar is at the correct position at the start and end of the transition
 * nav bar is always visible
-* nav bar is rotates
+* nav bar is at the correct position at the start and end of the transition
 
 The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
 
-### Non-Rotation animations and transitions
+For more examples of how a test looks like check `ChangeAppRotationTest` within the `Rotation` subdirectory.
 
-`NonRotationTestBase` was created to make it easier to write tests that do not involve rotation (e.g., `Pip`, `split screen` or `IME`).
-Tests that inherit from the class are automatically executed twice: once in portrait and once in landscape mode and the assertions are checked independently.
-Moreover, these tests inherit the following checks:
-* all regions on the screen are covered
-* status bar is always visible
-* nav bar is always visible
-
-The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-
-### Exceptional cases
-
-Tests that rotate the device should inherit from `RotationTestBase`.
-This class allows the test to be freely configured and does not provide any assertions.  
-
-
-### Example
-
-Start by defining common or error prone transitions using `TransitionRunner`.
-```kotlin
-@LargeTest
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MyTest(
-    beginRotationName: String,
-    beginRotation: Int
-) : NonRotationTestBase(beginRotationName, beginRotation) {
-    init {
-        mTestApp = MyAppHelper(InstrumentationRegistry.getInstrumentation())
-    }
-
-    override val transitionToRun: TransitionRunner
-        get() = TransitionRunner.newBuilder()
-            .withTag("myTest")
-            .recordAllRuns()
-            .runBefore { device.pressHome() }
-            .runBefore { device.waitForIdle() }
-            .run { testApp.open() }
-            .runAfter{ testApp.exit() }
-            .repeat(2)
-            .includeJankyRuns()
-            .build()
-
-    @Test
-    fun myWMTest() {
-        checkResults {
-            WmTraceSubject.assertThat(it)
-                    .showsAppWindow(MyTestApp)
-                    .forAllEntries()
-        }
-    }
-
-    @Test
-    fun mySFTest() {
-        checkResults {
-            LayersTraceSubject.assertThat(it)
-                    .showsLayer(MyTestApp)
-                    .forAllEntries()
-        }
-    }
-}
-```
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index e60764f..80282c3 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -33,7 +33,6 @@
 import android.os.Bundle
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.util.proto.ProtoOutputStream
 import android.view.InputDevice
 import android.view.inputmethod.InputMethodInfo
@@ -47,9 +46,7 @@
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
-import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
-import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -234,631 +231,326 @@
     }
 
     @Test
-    fun testDefaultUi_getKeyboardLayouts() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
-            assertNotEquals(
-                "Default UI: Keyboard layout API should not return empty array",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue(
-                "Default UI: Keyboard layout API should provide English(US) layout",
-                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-        }
+    fun testGetKeyboardLayouts() {
+        val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+        assertNotEquals(
+            "Keyboard layout API should not return empty array",
+            0,
+            keyboardLayouts.size
+        )
+        assertTrue(
+            "Keyboard layout API should provide English(US) layout",
+            hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+        )
     }
 
     @Test
-    fun testNewUi_getKeyboardLayouts() {
-        NewSettingsApiFlag(true).use {
-            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
-            assertNotEquals(
-                "New UI: Keyboard layout API should not return empty array",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue(
-                "New UI: Keyboard layout API should provide English(US) layout",
-                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-        }
+    fun testGetKeyboardLayout() {
+        val keyboardLayout =
+            keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+        assertEquals("getKeyboardLayout API should return correct Layout from " +
+                "available layouts",
+            ENGLISH_US_LAYOUT_DESCRIPTOR,
+            keyboardLayout!!.descriptor
+        )
     }
 
     @Test
-    fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
-            assertNotEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue(
-                "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
-                        "layout",
-                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
+    fun testGetSetKeyboardLayoutForInputDevice_withImeInfo() {
+        val imeSubtype = createImeSubtype()
 
-            val vendorSpecificKeyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
-                    vendorSpecificKeyboardDevice.identifier
-                )
-            assertEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
-                        "specific layout",
+        keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+            keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+            ENGLISH_UK_LAYOUT_DESCRIPTOR
+        )
+        var result =
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+            )
+        assertEquals(
+            "getKeyboardLayoutForInputDevice API should return the set layout",
+            ENGLISH_UK_LAYOUT_DESCRIPTOR,
+            result.layoutDescriptor
+        )
+
+        // This should replace previously set layout
+        keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+            keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+            ENGLISH_US_LAYOUT_DESCRIPTOR
+        )
+        result =
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+            )
+        assertEquals(
+            "getKeyboardLayoutForInputDevice API should return the last set layout",
+            ENGLISH_US_LAYOUT_DESCRIPTOR,
+            result.layoutDescriptor
+        )
+    }
+
+    @Test
+    fun testGetKeyboardLayoutListForInputDevice() {
+        // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+        var keyboardLayouts =
+            keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo,
+                createImeSubtypeForLanguageTag("hi-Latn")
+            )
+        assertNotEquals(
+            "getKeyboardLayoutListForInputDevice API should return the list of " +
+                    "supported layouts with matching script code",
+            0,
+            keyboardLayouts.size
+        )
+        assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+                "containing English(US) layout for hi-Latn",
+            containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+        )
+        assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+                "containing English(No script code) layout for hi-Latn",
+            containsLayout(
+                keyboardLayouts,
+                createLayoutDescriptor("keyboard_layout_english_without_script_code")
+            )
+        )
+
+        // Check Layouts for "hi" which by default uses 'Deva' script.
+        keyboardLayouts =
+            keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo,
+                createImeSubtypeForLanguageTag("hi")
+            )
+        assertEquals("getKeyboardLayoutListForInputDevice API should return empty " +
+                "list if no supported layouts available",
+            0,
+            keyboardLayouts.size
+        )
+
+        // If user manually selected some layout, always provide it in the layout list
+        val imeSubtype = createImeSubtypeForLanguageTag("hi")
+        keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+            keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+            ENGLISH_US_LAYOUT_DESCRIPTOR
+        )
+        keyboardLayouts =
+            keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo,
+                imeSubtype
+            )
+        assertEquals("getKeyboardLayoutListForInputDevice API should return user " +
+                "selected layout even if the script is incompatible with IME",
                 1,
-                vendorSpecificKeyboardLayouts.size
-            )
-            assertEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
-                        "layout",
-                VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
-                vendorSpecificKeyboardLayouts[0].descriptor
-            )
-        }
-    }
+            keyboardLayouts.size
+        )
 
-    @Test
-    fun testNewUi_getKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
-            assertNotEquals(
-                    "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
-                    0,
-                    keyboardLayouts.size
-            )
-            assertTrue(
-                    "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
-                            "layout",
-                    hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            assertNull(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
-                        "nothing was set",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-
-            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            val keyboardLayout =
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            assertEquals(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
-                        "layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayout
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertNull(
-                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
-                        "even after setCurrentKeyboardLayoutForInputDevice",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-
-            val keyboardLayouts =
-                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
-                    keyboardDevice.identifier
-                )
-            assertEquals(
-                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
-                        "layout",
-                1,
-                keyboardLayouts.size
-            )
-            assertEquals(
-                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
-                        "English(US) layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayouts[0]
-            )
-            assertEquals(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
-                        "English(US) layout (Auto select the first enabled layout)",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-
-            keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
-                0,
-                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
-                    keyboardDevice.identifier
-                ).size
-            )
-            assertNull(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
-                        "the enabled layout is removed",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-
-            assertEquals(
-                "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
-                        "an empty array",
-                0,
-                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
-                    keyboardDevice.identifier
-                ).size
-            )
-            assertNull(
-                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_switchKeyboardLayout() {
-        NewSettingsApiFlag(false).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
-                        "English(US) layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-
-            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-
-            // Throws null pointer because trying to show toast using TestLooper
-            assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
-            assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
-                    "English(UK) layout",
-                ENGLISH_UK_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_switchKeyboardLayout() {
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-
-            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-            testLooper.dispatchAll()
-
-            assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
-                    "null",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getKeyboardLayout() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayout =
-                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
-            assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
-                    "available layouts",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayout!!.descriptor
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getKeyboardLayout() {
-        NewSettingsApiFlag(true).use {
-            val keyboardLayout =
-                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
-            assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
-                    "available layouts",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayout!!.descriptor
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
-        NewSettingsApiFlag(false).use {
-            val imeSubtype = createImeSubtype()
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "Default UI: getKeyboardLayoutForInputDevice API should always return " +
-                        "KeyboardLayoutSelectionResult.FAILED",
-                KeyboardLayoutSelectionResult.FAILED,
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
-        NewSettingsApiFlag(true).use {
-            val imeSubtype = createImeSubtype()
-
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            var result =
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
-                )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
-                ENGLISH_UK_LAYOUT_DESCRIPTOR,
-                result.layoutDescriptor
-            )
-
-            // This should replace previously set layout
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            result =
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
-                )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                result.layoutDescriptor
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+        // Special case Japanese: UScript ignores provided script code for certain language tags
+        // Should manually match provided script codes and then rely on Uscript to derive
+        // script from language tags and match those.
+        keyboardLayouts =
+            keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtype()
-                )
-            assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
-                    "return empty array",
-                0,
-                keyboardLayouts.size
+                    createImeSubtypeForLanguageTag("ja-Latn-JP")
             )
-        }
-    }
+        assertNotEquals(
+            "getKeyboardLayoutListForInputDevice API should return the list of " +
+                    "supported layouts with matching script code for ja-Latn-JP",
+            0,
+            keyboardLayouts.size
+        )
+        assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+                "containing English(US) layout for ja-Latn-JP",
+            containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+        )
+        assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+                "containing English(No script code) layout for ja-Latn-JP",
+            containsLayout(
+                keyboardLayouts,
+                createLayoutDescriptor("keyboard_layout_english_without_script_code")
+            )
+        )
 
-    @Test
-    fun testNewUi_getKeyboardLayoutListForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
-            var keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+        // If script code not explicitly provided for Japanese should rely on Uscript to find
+        // derived script code and hence no suitable layout will be found.
+        keyboardLayouts =
+            keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("hi-Latn")
-                )
-            assertNotEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
-                        "supported layouts with matching script code",
-                0,
-                keyboardLayouts.size
+                    createImeSubtypeForLanguageTag("ja-JP")
             )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(US) layout for hi-Latn",
-                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(No script code) layout for hi-Latn",
-                containsLayout(
-                    keyboardLayouts,
-                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
-                )
-            )
+        assertEquals(
+            "getKeyboardLayoutListForInputDevice API should return empty list of " +
+                    "supported layouts with matching script code for ja-JP",
+            0,
+            keyboardLayouts.size
+        )
 
-            // Check Layouts for "hi" which by default uses 'Deva' script.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("hi")
-                )
-            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
-                    "list if no supported layouts available",
-                0,
-                keyboardLayouts.size
+        // If IME doesn't have a corresponding language tag, then should show all available
+        // layouts no matter the script code.
+        keyboardLayouts =
+            keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, null
             )
-
-            // If user manually selected some layout, always provide it in the layout list
-            val imeSubtype = createImeSubtypeForLanguageTag("hi")
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    imeSubtype
-                )
-            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
-                    "selected layout even if the script is incompatible with IME",
-                    1,
-                keyboardLayouts.size
-            )
-
-            // Special case Japanese: UScript ignores provided script code for certain language tags
-            // Should manually match provided script codes and then rely on Uscript to derive
-            // script from language tags and match those.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                        keyboardDevice.identifier, USER_ID, imeInfo,
-                        createImeSubtypeForLanguageTag("ja-Latn-JP")
-                )
-            assertNotEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
-                        "supported layouts with matching script code for ja-Latn-JP",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(US) layout for ja-Latn-JP",
-                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(No script code) layout for ja-Latn-JP",
-                containsLayout(
-                    keyboardLayouts,
-                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
-                )
-            )
-
-            // If script code not explicitly provided for Japanese should rely on Uscript to find
-            // derived script code and hence no suitable layout will be found.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                        keyboardDevice.identifier, USER_ID, imeInfo,
-                        createImeSubtypeForLanguageTag("ja-JP")
-                )
-            assertEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
-                        "supported layouts with matching script code for ja-JP",
-                0,
-                keyboardLayouts.size
-            )
-
-            // If IME doesn't have a corresponding language tag, then should show all available
-            // layouts no matter the script code.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, null
-                )
-            assertNotEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
-                    "language tag or subtype not provided",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
-                "layouts if language tag or subtype not provided",
-                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
-                "layouts if language tag or subtype not provided",
-                containsLayout(
-                    keyboardLayouts,
-                    createLayoutDescriptor("keyboard_layout_russian")
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
-        NewSettingsApiFlag(true).use {
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("en-US"),
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("en-GB"),
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("de"),
-                GERMAN_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("fr-FR"),
-                createLayoutDescriptor("keyboard_layout_french")
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("ru"),
+        assertNotEquals(
+            "getKeyboardLayoutListForInputDevice API should return all layouts if" +
+                "language tag or subtype not provided",
+            0,
+            keyboardLayouts.size
+        )
+        assertTrue("getKeyboardLayoutListForInputDevice API should contain Latin " +
+            "layouts if language tag or subtype not provided",
+            containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+        )
+        assertTrue("getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+            "layouts if language tag or subtype not provided",
+            containsLayout(
+                keyboardLayouts,
                 createLayoutDescriptor("keyboard_layout_russian")
             )
-            assertEquals(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
-                        "KeyboardLayoutSelectionResult.FAILED when no layout available",
-                KeyboardLayoutSelectionResult.FAILED,
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("it")
-                )
-            )
-            assertEquals(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
-                        "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
-                        "available",
-                KeyboardLayoutSelectionResult.FAILED,
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("en-Deva")
-                )
-            )
-        }
+        )
     }
 
     @Test
-    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
-        NewSettingsApiFlag(true).use {
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
-                ENGLISH_US_LAYOUT_DESCRIPTOR
+    fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTag("en-US"),
+            ENGLISH_US_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTag("en-GB"),
+            ENGLISH_UK_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTag("de"),
+            GERMAN_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTag("fr-FR"),
+            createLayoutDescriptor("keyboard_layout_french")
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTag("ru"),
+            createLayoutDescriptor("keyboard_layout_russian")
+        )
+        assertEquals(
+            "getDefaultKeyboardLayoutForInputDevice should return " +
+                    "KeyboardLayoutSelectionResult.FAILED when no layout available",
+            KeyboardLayoutSelectionResult.FAILED,
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo,
+                createImeSubtypeForLanguageTag("it")
             )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
-                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
-            )
-            // Try to match layout type even if country doesn't match
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
-                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
-            )
-            // Choose layout based on layout type priority, if layout type is not provided by IME
-            // (Qwerty > Dvorak > Extended)
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
-                GERMAN_LAYOUT_DESCRIPTOR
-            )
-            // Wrong layout type should match with language if provided layout type not available
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
-                GERMAN_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
-                createLayoutDescriptor("keyboard_layout_french")
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
-                createLayoutDescriptor("keyboard_layout_russian_qwerty")
-            )
-            // If layout type is empty then prioritize KCM with empty layout type
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
-                createLayoutDescriptor("keyboard_layout_russian")
-            )
-            assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+        )
+        assertEquals(
+            "getDefaultKeyboardLayoutForInputDevice should return " +
                     "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
                     "available",
-                KeyboardLayoutSelectionResult.FAILED,
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
-                )
+            KeyboardLayoutSelectionResult.FAILED,
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo,
+                createImeSubtypeForLanguageTag("en-Deva")
             )
-        }
+        )
     }
 
     @Test
-    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
-        NewSettingsApiFlag(true).use {
-            val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
-            // Should return English dvorak even if IME current layout is French, since HW says the
-            // keyboard is a Dvorak keyboard
-            assertCorrectLayout(
-                englishDvorakKeyboardDevice,
-                frenchSubtype,
-                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+    fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+            ENGLISH_US_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+            createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+        )
+        // Try to match layout type even if country doesn't match
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+            createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+        )
+        // Choose layout based on layout type priority, if layout type is not provided by IME
+        // (Qwerty > Dvorak > Extended)
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+            ENGLISH_US_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+            ENGLISH_UK_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+            GERMAN_LAYOUT_DESCRIPTOR
+        )
+        // Wrong layout type should match with language if provided layout type not available
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+            GERMAN_LAYOUT_DESCRIPTOR
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+            createLayoutDescriptor("keyboard_layout_french")
+        )
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+            createLayoutDescriptor("keyboard_layout_russian_qwerty")
+        )
+        // If layout type is empty then prioritize KCM with empty layout type
+        assertCorrectLayout(
+            keyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+            createLayoutDescriptor("keyboard_layout_russian")
+        )
+        assertEquals("getDefaultKeyboardLayoutForInputDevice should return " +
+                "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+                "available",
+            KeyboardLayoutSelectionResult.FAILED,
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo,
+                createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
             )
+        )
+    }
 
-            // Back to back changing HW keyboards with same product and vendor ID but different
-            // language and layout type should configure the layouts correctly.
-            assertCorrectLayout(
-                englishQwertyKeyboardDevice,
-                frenchSubtype,
-                createLayoutDescriptor("keyboard_layout_english_us")
-            )
+    @Test
+    fun testGetDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+        val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+        // Should return English dvorak even if IME current layout is French, since HW says the
+        // keyboard is a Dvorak keyboard
+        assertCorrectLayout(
+            englishDvorakKeyboardDevice,
+            frenchSubtype,
+            createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+        )
 
-            // Fallback to IME information if the HW provided layout script is incompatible with the
-            // provided IME subtype
-            assertCorrectLayout(
-                englishDvorakKeyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
-                createLayoutDescriptor("keyboard_layout_russian")
-            )
-        }
+        // Back to back changing HW keyboards with same product and vendor ID but different
+        // language and layout type should configure the layouts correctly.
+        assertCorrectLayout(
+            englishQwertyKeyboardDevice,
+            frenchSubtype,
+            createLayoutDescriptor("keyboard_layout_english_us")
+        )
+
+        // Fallback to IME information if the HW provided layout script is incompatible with the
+        // provided IME subtype
+        assertCorrectLayout(
+            englishDvorakKeyboardDevice,
+            createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+            createLayoutDescriptor("keyboard_layout_russian")
+        )
     }
 
     @Test
@@ -867,27 +559,25 @@
                 KeyboardLayoutManager.ImeInfo(0, imeInfo,
                         createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
         Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
-            ExtendedMockito.verify {
-                FrameworkStatsLog.write(
-                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
-                        ArgumentMatchers.anyBoolean(),
-                        ArgumentMatchers.eq(keyboardDevice.vendorId),
-                        ArgumentMatchers.eq(keyboardDevice.productId),
-                        ArgumentMatchers.eq(
-                            createByteArray(
-                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
-                                LAYOUT_TYPE_DEFAULT,
-                                GERMAN_LAYOUT_NAME,
-                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
-                                "de-Latn",
-                                LAYOUT_TYPE_QWERTZ
-                            ),
+        keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+        ExtendedMockito.verify {
+            FrameworkStatsLog.write(
+                    ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                    ArgumentMatchers.anyBoolean(),
+                    ArgumentMatchers.eq(keyboardDevice.vendorId),
+                    ArgumentMatchers.eq(keyboardDevice.productId),
+                    ArgumentMatchers.eq(
+                        createByteArray(
+                            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+                            LAYOUT_TYPE_DEFAULT,
+                            GERMAN_LAYOUT_NAME,
+                            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+                            "de-Latn",
+                            LAYOUT_TYPE_QWERTZ
                         ),
-                        ArgumentMatchers.eq(keyboardDevice.deviceBus),
-                )
-            }
+                    ),
+                    ArgumentMatchers.eq(keyboardDevice.deviceBus),
+            )
         }
     }
 
@@ -897,27 +587,25 @@
                 KeyboardLayoutManager.ImeInfo(0, imeInfo,
                         createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
         Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
-            ExtendedMockito.verify {
-                FrameworkStatsLog.write(
-                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
-                        ArgumentMatchers.anyBoolean(),
-                        ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
-                        ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
-                        ArgumentMatchers.eq(
-                            createByteArray(
-                                "en",
-                                LAYOUT_TYPE_QWERTY,
-                                ENGLISH_US_LAYOUT_NAME,
-                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
-                                "de-Latn",
-                                LAYOUT_TYPE_QWERTZ
-                            )
-                        ),
-                        ArgumentMatchers.eq(keyboardDevice.deviceBus),
-                )
-            }
+        keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
+        ExtendedMockito.verify {
+            FrameworkStatsLog.write(
+                    ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                    ArgumentMatchers.anyBoolean(),
+                    ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
+                    ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
+                    ArgumentMatchers.eq(
+                        createByteArray(
+                            "en",
+                            LAYOUT_TYPE_QWERTY,
+                            ENGLISH_US_LAYOUT_NAME,
+                            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
+                            "de-Latn",
+                            LAYOUT_TYPE_QWERTZ
+                        )
+                    ),
+                    ArgumentMatchers.eq(keyboardDevice.deviceBus),
+            )
         }
     }
 
@@ -925,27 +613,25 @@
     fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
         val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
         Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
-            ExtendedMockito.verify {
-                FrameworkStatsLog.write(
-                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
-                        ArgumentMatchers.anyBoolean(),
-                        ArgumentMatchers.eq(keyboardDevice.vendorId),
-                        ArgumentMatchers.eq(keyboardDevice.productId),
-                        ArgumentMatchers.eq(
-                            createByteArray(
-                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
-                                LAYOUT_TYPE_DEFAULT,
-                                "Default",
-                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
-                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
-                                LAYOUT_TYPE_DEFAULT
-                            ),
+        keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+        ExtendedMockito.verify {
+            FrameworkStatsLog.write(
+                    ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                    ArgumentMatchers.anyBoolean(),
+                    ArgumentMatchers.eq(keyboardDevice.vendorId),
+                    ArgumentMatchers.eq(keyboardDevice.productId),
+                    ArgumentMatchers.eq(
+                        createByteArray(
+                            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+                            LAYOUT_TYPE_DEFAULT,
+                            "Default",
+                            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+                            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+                            LAYOUT_TYPE_DEFAULT
                         ),
-                        ArgumentMatchers.eq(keyboardDevice.deviceBus),
-                )
-            }
+                    ),
+                    ArgumentMatchers.eq(keyboardDevice.deviceBus),
+            )
         }
     }
 
@@ -953,19 +639,17 @@
     fun testConfigurationNotLogged_onInputDeviceChanged() {
         val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
         Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
-            ExtendedMockito.verify({
-                FrameworkStatsLog.write(
-                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
-                        ArgumentMatchers.anyBoolean(),
-                        ArgumentMatchers.anyInt(),
-                        ArgumentMatchers.anyInt(),
-                        ArgumentMatchers.any(ByteArray::class.java),
-                        ArgumentMatchers.anyInt(),
-                )
-            }, Mockito.times(0))
-        }
+        keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+        ExtendedMockito.verify({
+            FrameworkStatsLog.write(
+                    ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                    ArgumentMatchers.anyBoolean(),
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.any(ByteArray::class.java),
+                    ArgumentMatchers.anyInt(),
+            )
+        }, Mockito.times(0))
     }
 
     @Test
@@ -975,18 +659,16 @@
         Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice(
             ArgumentMatchers.eq(keyboardDevice.id)
         )
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
-            ExtendedMockito.verify(
-                notificationManager,
-                Mockito.times(1)
-            ).notifyAsUser(
-                ArgumentMatchers.isNull(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any(),
-                ArgumentMatchers.any()
-            )
-        }
+        keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+        ExtendedMockito.verify(
+            notificationManager,
+            Mockito.times(1)
+        ).notifyAsUser(
+            ArgumentMatchers.isNull(),
+            ArgumentMatchers.anyInt(),
+            ArgumentMatchers.any(),
+            ArgumentMatchers.any()
+        )
     }
 
     @Test
@@ -996,18 +678,16 @@
         Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice(
             ArgumentMatchers.eq(keyboardDevice.id)
         )
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
-            ExtendedMockito.verify(
-                notificationManager,
-                Mockito.never()
-            ).notifyAsUser(
-                ArgumentMatchers.isNull(),
-                ArgumentMatchers.anyInt(),
-                ArgumentMatchers.any(),
-                ArgumentMatchers.any()
-            )
-        }
+        keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+        ExtendedMockito.verify(
+            notificationManager,
+            Mockito.never()
+        ).notifyAsUser(
+            ArgumentMatchers.isNull(),
+            ArgumentMatchers.anyInt(),
+            ArgumentMatchers.any(),
+            ArgumentMatchers.any()
+        )
     }
 
     private fun assertCorrectLayout(
@@ -1019,7 +699,7 @@
             device.identifier, USER_ID, imeInfo, imeSubtype
         )
         assertEquals(
-            "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+            "getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
             expectedLayout,
             result.layoutDescriptor
         )
@@ -1123,21 +803,4 @@
         info.serviceInfo.name = RECEIVER_NAME
         return info
     }
-
-    private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
-        init {
-            Settings.Global.putString(
-                context.contentResolver,
-                "settings_new_keyboard_ui", enabled.toString()
-            )
-        }
-
-        override fun close() {
-            Settings.Global.putString(
-                context.contentResolver,
-                "settings_new_keyboard_ui",
-                ""
-            )
-        }
-    }
 }
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 093923f..1fdf97a 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
-import android.util.LongArrayQueue;
 import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index b506d74..56845ae 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -21,10 +21,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -34,9 +33,7 @@
 import android.hardware.usb.flags.Flags;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -64,7 +61,7 @@
     @Mock
     private UsbSettingsManager mUsbSettingsManager;
     @Mock
-    private IUsbOperationInternal mIUsbOperationInternal;
+    private IUsbOperationInternal mCallback;
 
     private static final String TEST_PORT_ID = "123";
 
@@ -77,98 +74,105 @@
     private UsbService mUsbService;
 
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
         MockitoAnnotations.initMocks(this);
-        mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager, mUserManager,
-                mUsbSettingsManager);
+
+        when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
+                eq(mCallback), any())).thenReturn(true);
+
+        mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
+                mUserManager, mUsbSettingsManager);
+    }
+
+    private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+        assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
+                TEST_TRANSACTION_ID, mCallback, uid));
+
+        verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+                enable, TEST_TRANSACTION_ID, mCallback, null);
+        verifyZeroInteractions(mCallback);
+
+        clearInvocations(mUsbPortManager);
+        clearInvocations(mCallback);
+    }
+
+    private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+        assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
+                TEST_TRANSACTION_ID, mCallback, uid));
+
+        verifyZeroInteractions(mUsbPortManager);
+        verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+
+        clearInvocations(mUsbPortManager);
+        clearInvocations(mCallback);
     }
 
     /**
      * Verify enableUsbData successfully disables USB port without error
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
-    public void usbPort_SuccessfullyDisabled() {
-        boolean enableState = false;
-        when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
-                mIUsbOperationInternal, null)).thenReturn(true);
-
-        assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
-                TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
-
-        verify(mUsbPortManager, times(1)).enableUsbData(TEST_PORT_ID,
-                enableState, TEST_TRANSACTION_ID, mIUsbOperationInternal, null);
-        verifyZeroInteractions(mIUsbOperationInternal);
+    public void disableUsb_successfullyDisable() {
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
     }
 
     /**
      * Verify enableUsbData successfully enables USB port without error given no other stakers
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
-    public void usbPortWhenNoOtherStakers_SuccessfullyEnabledUsb() {
-        boolean enableState = true;
-        when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
-                mIUsbOperationInternal, null))
-                .thenReturn(true);
-
-        assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
-                TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
-        verifyZeroInteractions(mIUsbOperationInternal);
+    public void enableUsbWhenNoOtherStakers_successfullyEnable() {
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
     }
 
     /**
      * Verify enableUsbData does not enable USB port if other stakers are present
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
-    public void usbPortWithOtherStakers_DoesNotToEnableUsb() throws RemoteException {
-        mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
-                mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
-        clearInvocations(mUsbPortManager);
+    public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
 
-        assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, true,
-                TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_SECOND_CALLER_ID));
+        assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+    }
 
-        verifyZeroInteractions(mUsbPortManager);
-        verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+    /**
+     * Verify enableUsbData successfully enables USB port when the last staker is removed
+     */
+    @Test
+    public void enableUsbByTheOnlyStaker_successfullyEnable() {
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
     }
 
     /**
      * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
-    public void enableUsbWhileDockedWhenThereAreOtherStakers_DoesNotEnableUsb()
+    public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
             throws RemoteException {
-        mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
-                mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
 
-        mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, 0,
-                mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+        mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
+                mCallback, TEST_SECOND_CALLER_ID);
 
-        verify(mUsbPortManager, never()).enableUsbDataWhileDocked(any(),
-                anyLong(), any(), any());
-        verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+        verifyZeroInteractions(mUsbPortManager);
+        verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
     }
 
     /**
-     * Verify enableUsbDataWhileDockedInternal does  enable USB port if other stakers are
+     * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
      * not present
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
-    public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnableUsb()
-            throws RemoteException {
+    public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
         mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
-                mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+                mCallback, TEST_SECOND_CALLER_ID);
 
-        verify(mUsbPortManager, times(1))
-                .enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
-                        mIUsbOperationInternal, null);
-        verifyZeroInteractions(mIUsbOperationInternal);
+        verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
+                        mCallback, null);
+        verifyZeroInteractions(mCallback);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 22c3fd8..8c2186a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -28,10 +28,18 @@
 
     private final Long mVersion;
     private final DataLabels mDataLabels;
+    private final SecurityLabels mSecurityLabels;
+    private final ThirdPartyVerification mThirdPartyVerification;
 
-    public SafetyLabels(Long version, DataLabels dataLabels) {
+    public SafetyLabels(
+            Long version,
+            DataLabels dataLabels,
+            SecurityLabels securityLabels,
+            ThirdPartyVerification thirdPartyVerification) {
         this.mVersion = version;
         this.mDataLabels = dataLabels;
+        this.mSecurityLabels = securityLabels;
+        this.mThirdPartyVerification = thirdPartyVerification;
     }
 
     /** Returns the data label for the safety label */
@@ -54,6 +62,12 @@
         if (mDataLabels != null) {
             XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
         }
+        if (mSecurityLabels != null) {
+            XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toOdDomElements(doc));
+        }
+        if (mThirdPartyVerification != null) {
+            XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toOdDomElements(doc));
+        }
         return XmlUtils.listOf(safetyLabelsEle);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 6bf8ef3..0f7aa81 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -44,6 +44,22 @@
                                                 safetyLabelsEle,
                                                 XmlUtils.HR_TAG_DATA_LABELS,
                                                 false)));
-        return new SafetyLabels(version, dataLabels);
+        SecurityLabels securityLabels =
+                new SecurityLabelsFactory()
+                        .createFromHrElements(
+                                XmlUtils.listOf(
+                                        XmlUtils.getSingleChildElement(
+                                                safetyLabelsEle,
+                                                XmlUtils.HR_TAG_SECURITY_LABELS,
+                                                false)));
+        ThirdPartyVerification thirdPartyVerification =
+                new ThirdPartyVerificationFactory()
+                        .createFromHrElements(
+                                XmlUtils.listOf(
+                                        XmlUtils.getSingleChildElement(
+                                                safetyLabelsEle,
+                                                XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION,
+                                                false)));
+        return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
new file mode 100644
index 0000000..529b5036
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** Security Labels representation */
+public class SecurityLabels implements AslMarshallable {
+
+    private final Boolean mIsDataDeletable;
+    private final Boolean mIsDataEncrypted;
+
+    public SecurityLabels(Boolean isDataDeletable, Boolean isDataEncrypted) {
+        this.mIsDataDeletable = isDataDeletable;
+        this.mIsDataEncrypted = isDataEncrypted;
+    }
+
+    /** Creates an on-device DOM element from the {@link SecurityLabels}. */
+    @Override
+    public List<Element> toOdDomElements(Document doc) {
+        Element ele = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SECURITY_LABELS);
+        if (mIsDataDeletable != null) {
+            ele.appendChild(
+                    XmlUtils.createOdBooleanEle(
+                            doc, XmlUtils.OD_NAME_IS_DATA_DELETABLE, mIsDataDeletable));
+        }
+        if (mIsDataEncrypted != null) {
+            ele.appendChild(
+                    XmlUtils.createOdBooleanEle(
+                            doc, XmlUtils.OD_NAME_IS_DATA_ENCRYPTED, mIsDataEncrypted));
+        }
+        return XmlUtils.listOf(ele);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
new file mode 100644
index 0000000..8402452
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.asllib.marshallable;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLabels> {
+
+    /** Creates a {@link SecurityLabels} from the human-readable DOM element. */
+    @Override
+    public SecurityLabels createFromHrElements(List<Element> elements)
+            throws MalformedXmlException {
+        Element ele = XmlUtils.getSingleElement(elements);
+        if (ele == null) {
+            AslgenUtil.logI("No SecurityLabels found in hr format.");
+            return null;
+        }
+        Boolean isDataDeletable =
+                XmlUtils.getBoolAttr(ele, XmlUtils.HR_ATTR_IS_DATA_DELETABLE, false);
+        Boolean isDataEncrypted =
+                XmlUtils.getBoolAttr(ele, XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, false);
+        return new SecurityLabels(isDataDeletable, isDataEncrypted);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
new file mode 100644
index 0000000..a1b22f8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** ThirdPartyVerification representation. */
+public class ThirdPartyVerification implements AslMarshallable {
+
+    private final String mUrl;
+
+    public ThirdPartyVerification(String url) {
+        this.mUrl = url;
+    }
+
+    /** Creates an on-device DOM element from the {@link ThirdPartyVerification}. */
+    @Override
+    public List<Element> toOdDomElements(Document doc) {
+        Element ele =
+                XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION);
+        ele.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+        return XmlUtils.listOf(ele);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
new file mode 100644
index 0000000..c3e4964
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.asllib.marshallable;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class ThirdPartyVerificationFactory
+        implements AslMarshallableFactory<ThirdPartyVerification> {
+
+    /** Creates a {@link ThirdPartyVerification} from the human-readable DOM element. */
+    @Override
+    public ThirdPartyVerification createFromHrElements(List<Element> elements)
+            throws MalformedXmlException {
+        Element ele = XmlUtils.getSingleElement(elements);
+        if (ele == null) {
+            AslgenUtil.logI("No ThirdPartyVerification found in hr format.");
+            return null;
+        }
+
+        String url = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_URL);
+        return new ThirdPartyVerification(url);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 691f92f..ed3d7f8 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -32,6 +32,8 @@
     public static final String HR_TAG_DEVELOPER_INFO = "developer-info";
     public static final String HR_TAG_APP_INFO = "app-info";
     public static final String HR_TAG_DATA_LABELS = "data-labels";
+    public static final String HR_TAG_SECURITY_LABELS = "security-labels";
+    public static final String HR_TAG_THIRD_PARTY_VERIFICATION = "third-party-verification";
     public static final String HR_TAG_DATA_ACCESSED = "data-accessed";
     public static final String HR_TAG_DATA_COLLECTED = "data-collected";
     public static final String HR_TAG_DATA_SHARED = "data-shared";
@@ -46,6 +48,8 @@
     public static final String HR_ATTR_DATA_TYPE = "dataType";
     public static final String HR_ATTR_IS_COLLECTION_OPTIONAL = "isCollectionOptional";
     public static final String HR_ATTR_IS_SHARING_OPTIONAL = "isSharingOptional";
+    public static final String HR_ATTR_IS_DATA_DELETABLE = "isDataDeletable";
+    public static final String HR_ATTR_IS_DATA_ENCRYPTED = "isDataEncrypted";
     public static final String HR_ATTR_EPHEMERAL = "ephemeral";
     public static final String HR_ATTR_PURPOSES = "purposes";
     public static final String HR_ATTR_VERSION = "version";
@@ -98,6 +102,8 @@
     public static final String OD_NAME_VERSION = "version";
     public static final String OD_NAME_URL = "url";
     public static final String OD_NAME_SYSTEM_APP_SAFETY_LABEL = "system_app_safety_label";
+    public static final String OD_NAME_SECURITY_LABELS = "security_labels";
+    public static final String OD_NAME_THIRD_PARTY_VERIFICATION = "third_party_verification";
     public static final String OD_NAME_DATA_LABELS = "data_labels";
     public static final String OD_NAME_DATA_ACCESSED = "data_accessed";
     public static final String OD_NAME_DATA_COLLECTED = "data_collected";
@@ -105,6 +111,8 @@
     public static final String OD_NAME_PURPOSES = "purposes";
     public static final String OD_NAME_IS_COLLECTION_OPTIONAL = "is_collection_optional";
     public static final String OD_NAME_IS_SHARING_OPTIONAL = "is_sharing_optional";
+    public static final String OD_NAME_IS_DATA_DELETABLE = "is_data_deletable";
+    public static final String OD_NAME_IS_DATA_ENCRYPTED = "is_data_encrypted";
     public static final String OD_NAME_EPHEMERAL = "ephemeral";
 
     public static final String TRUE_STR = "true";
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
index 03e8ac6..54c80f6 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
@@ -17,9 +17,15 @@
 package com.android.asllib;
 
 import com.android.asllib.marshallable.AndroidSafetyLabelTest;
+import com.android.asllib.marshallable.AppInfoTest;
 import com.android.asllib.marshallable.DataCategoryTest;
 import com.android.asllib.marshallable.DataLabelsTest;
 import com.android.asllib.marshallable.DeveloperInfoTest;
+import com.android.asllib.marshallable.SafetyLabelsTest;
+import com.android.asllib.marshallable.SecurityLabelsTest;
+import com.android.asllib.marshallable.SystemAppSafetyLabelTest;
+import com.android.asllib.marshallable.ThirdPartyVerificationTest;
+import com.android.asllib.marshallable.TransparencyInfoTest;
 
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -28,8 +34,14 @@
 @Suite.SuiteClasses({
     AslgenTests.class,
     AndroidSafetyLabelTest.class,
-    DeveloperInfoTest.class,
+    AppInfoTest.class,
     DataCategoryTest.class,
     DataLabelsTest.class,
+    DeveloperInfoTest.class,
+    SafetyLabelsTest.class,
+    SecurityLabelsTest.class,
+    SystemAppSafetyLabelTest.class,
+    ThirdPartyVerificationTest.class,
+    TransparencyInfoTest.class
 })
 public class AllTests {}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index 5f43008..e2588d7 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -34,7 +34,8 @@
 @RunWith(JUnit4.class)
 public class AslgenTests {
     private static final String VALID_MAPPINGS_PATH = "com/android/asllib/validmappings";
-    private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("location", "contacts");
+    private static final List<String> VALID_MAPPINGS_SUBDIRS =
+            List.of("location", "contacts", "general");
     private static final String HR_XML_FILENAME = "hr.xml";
     private static final String OD_XML_FILENAME = "od.xml";
 
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
index b62620e..c52d6c8 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -32,6 +32,9 @@
     private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
     private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
     private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml";
+    private static final String WITH_SECURITY_LABELS_FILE_NAME = "with-security-labels.xml";
+    private static final String WITH_THIRD_PARTY_VERIFICATION_FILE_NAME =
+            "with-third-party-verification.xml";
 
     private Document mDoc = null;
 
@@ -62,6 +65,20 @@
         testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
     }
 
+    /** Test for safety labels with security labels. */
+    @Test
+    public void testSafetyLabelsWithSecurityLabels() throws Exception {
+        System.out.println("starting testSafetyLabelsWithSecurityLabels.");
+        testHrToOdSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME);
+    }
+
+    /** Test for safety labels with third party verification. */
+    @Test
+    public void testSafetyLabelsWithThirdPartyVerification() throws Exception {
+        System.out.println("starting testSafetyLabelsWithThirdPartyVerification.");
+        testHrToOdSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME);
+    }
+
     private void hrToOdExpectException(String fileName) {
         TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
     }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
new file mode 100644
index 0000000..c0d0d72
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.asllib.marshallable;
+
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class SecurityLabelsTest {
+    private static final String SECURITY_LABELS_HR_PATH = "com/android/asllib/securitylabels/hr";
+    private static final String SECURITY_LABELS_OD_PATH = "com/android/asllib/securitylabels/od";
+
+    public static final List<String> OPTIONAL_FIELD_NAMES =
+            List.of("isDataDeletable", "isDataEncrypted");
+
+    private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+
+    private Document mDoc = null;
+
+    /** Logic for setting up tests (empty if not yet needed). */
+    public static void main(String[] params) throws Exception {}
+
+    @Before
+    public void setUp() throws Exception {
+        System.out.println("set up.");
+        mDoc = TestUtils.document();
+    }
+
+    /** Test for all fields valid. */
+    @Test
+    public void testAllFieldsValid() throws Exception {
+        System.out.println("starting testAllFieldsValid.");
+        testHrToOdSecurityLabels(ALL_FIELDS_VALID_FILE_NAME);
+    }
+
+    /** Tests missing optional fields passes. */
+    @Test
+    public void testMissingOptionalFields() throws Exception {
+        for (String optField : OPTIONAL_FIELD_NAMES) {
+            var ele =
+                    TestUtils.getElementsFromResource(
+                            Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+            ele.get(0).removeAttribute(optField);
+            SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElements(ele);
+            securityLabels.toOdDomElements(mDoc);
+        }
+    }
+
+    private void testHrToOdSecurityLabels(String fileName) throws Exception {
+        TestUtils.testHrToOd(
+                mDoc,
+                new SecurityLabelsFactory(),
+                SECURITY_LABELS_HR_PATH,
+                SECURITY_LABELS_OD_PATH,
+                fileName);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
new file mode 100644
index 0000000..ab8e85c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class ThirdPartyVerificationTest {
+    private static final String THIRD_PARTY_VERIFICATION_HR_PATH =
+            "com/android/asllib/thirdpartyverification/hr";
+    private static final String THIRD_PARTY_VERIFICATION_OD_PATH =
+            "com/android/asllib/thirdpartyverification/od";
+
+    private static final String VALID_FILE_NAME = "valid.xml";
+    private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
+
+    private Document mDoc = null;
+
+    /** Logic for setting up tests (empty if not yet needed). */
+    public static void main(String[] params) throws Exception {}
+
+    @Before
+    public void setUp() throws Exception {
+        System.out.println("set up.");
+        mDoc = TestUtils.document();
+    }
+
+    /** Test for valid. */
+    @Test
+    public void testValid() throws Exception {
+        System.out.println("starting testValid.");
+        testHrToOdThirdPartyVerification(VALID_FILE_NAME);
+    }
+
+    /** Tests missing url. */
+    @Test
+    public void testMissingUrl() throws Exception {
+        System.out.println("starting testMissingUrl.");
+        hrToOdExpectException(MISSING_URL_FILE_NAME);
+    }
+
+    private void hrToOdExpectException(String fileName) {
+        TestUtils.hrToOdExpectException(
+                new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_HR_PATH, fileName);
+    }
+
+    private void testHrToOdThirdPartyVerification(String fileName) throws Exception {
+        TestUtils.testHrToOd(
+                mDoc,
+                new ThirdPartyVerificationFactory(),
+                THIRD_PARTY_VERIFICATION_HR_PATH,
+                THIRD_PARTY_VERIFICATION_OD_PATH,
+                fileName);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
new file mode 100644
index 0000000..940e48a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
@@ -0,0 +1,6 @@
+<safety-labels version="12345">
+    <security-labels
+        isDataDeletable="true"
+        isDataEncrypted="false"
+    />
+</safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
new file mode 100644
index 0000000..bfbc5ae
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
@@ -0,0 +1,4 @@
+<safety-labels version="12345">
+<third-party-verification url="www.example.com">
+    </third-party-verification>
+</safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
new file mode 100644
index 0000000..b39c562b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="safety_labels">
+    <long name="version" value="12345"/>
+    <pbundle_as_map name="security_labels">
+        <boolean name="is_data_deletable" value="true" />
+        <boolean name="is_data_encrypted" value="false" />
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
new file mode 100644
index 0000000..10653ff
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
@@ -0,0 +1,6 @@
+<pbundle_as_map name="safety_labels">
+    <long name="version" value="12345"/>
+    <pbundle_as_map name="third_party_verification">
+        <string name="url" value="www.example.com"/>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml
new file mode 100644
index 0000000..e2fb592
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml
@@ -0,0 +1,4 @@
+<security-labels
+    isDataDeletable="true"
+    isDataEncrypted="false">
+</security-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml
new file mode 100644
index 0000000..7b2f656
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml
@@ -0,0 +1,4 @@
+<pbundle_as_map name="security_labels">
+    <boolean name="is_data_deletable" value="true" />
+    <boolean name="is_data_encrypted" value="false" />
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml
new file mode 100644
index 0000000..6738ac2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml
@@ -0,0 +1 @@
+<third-party-verification></third-party-verification>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml
new file mode 100644
index 0000000..2a664f2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml
@@ -0,0 +1 @@
+<third-party-verification url="www.example.com"></third-party-verification>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml
new file mode 100644
index 0000000..dbeb592
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml
@@ -0,0 +1,3 @@
+<pbundle_as_map name="third_party_verification">
+    <string name="url" value="www.example.com"/>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
new file mode 100644
index 0000000..36beb93
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -0,0 +1,35 @@
+<app-metadata-bundles version="123">
+    <system-app-safety-label url="www.example.com">
+    </system-app-safety-label>
+    <safety-labels version="12345">
+        <data-labels>
+            <data-shared dataCategory="location"
+                dataType="approx_location"
+                isSharingOptional="false"
+                ephemeral="false"
+                purposes="app_functionality" />
+            <data-shared dataCategory="location"
+                dataType="precise_location"
+                isSharingOptional="true"
+                ephemeral="true"
+                purposes="app_functionality|analytics" />
+        </data-labels>
+        <security-labels
+            isDataDeletable="true"
+            isDataEncrypted="false"
+        />
+        <third-party-verification url="www.example.com">
+        </third-party-verification>
+    </safety-labels>
+    <transparency-info>
+        <developer-info
+            name="max"
+            email="max@example.com"
+            address="111 blah lane"
+            countryRegion="US"
+            relationship="aosp"
+            website="example.com"
+            appDeveloperRegistryId="registry_id" />
+        <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
+    </transparency-info>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
new file mode 100644
index 0000000..db21280
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -0,0 +1,70 @@
+<bundle>
+    <long name="version" value="123"/>
+    <pbundle_as_map name="safety_labels">
+        <long name="version" value="12345"/>
+        <pbundle_as_map name="data_labels">
+            <pbundle_as_map name="data_shared">
+                <pbundle_as_map name="location">
+                    <pbundle_as_map name="approx_location">
+                        <int-array name="purposes" num="1">
+                            <item value="1"/>
+                        </int-array>
+                        <boolean name="is_sharing_optional" value="false"/>
+                        <boolean name="ephemeral" value="false"/>
+                    </pbundle_as_map>
+                    <pbundle_as_map name="precise_location">
+                        <int-array name="purposes" num="2">
+                            <item value="1"/>
+                            <item value="2"/>
+                        </int-array>
+                        <boolean name="is_sharing_optional" value="true"/>
+                        <boolean name="ephemeral" value="true"/>
+                    </pbundle_as_map>
+                </pbundle_as_map>
+            </pbundle_as_map>
+        </pbundle_as_map>
+        <pbundle_as_map name="security_labels">
+            <boolean name="is_data_deletable" value="true"/>
+            <boolean name="is_data_encrypted" value="false"/>
+        </pbundle_as_map>
+        <pbundle_as_map name="third_party_verification">
+            <string name="url" value="www.example.com"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
+    <pbundle_as_map name="system_app_safety_label">
+        <string name="url" value="www.example.com"/>
+    </pbundle_as_map>
+    <pbundle_as_map name="transparency_info">
+        <pbundle_as_map name="developer_info">
+            <string name="name" value="max"/>
+            <string name="email" value="max@example.com"/>
+            <string name="address" value="111 blah lane"/>
+            <string name="country_region" value="US"/>
+            <long name="relationship" value="5"/>
+            <string name="website" value="example.com"/>
+        </pbundle_as_map>
+        <pbundle_as_map name="app_info">
+            <string name="title" value="beervision"/>
+            <string name="description" value="a beer app"/>
+            <boolean name="contains_ads" value="true"/>
+            <boolean name="obey_aps" value="false"/>
+            <boolean name="ads_fingerprinting" value="false"/>
+            <boolean name="security_fingerprinting" value="false"/>
+            <string name="privacy_policy" value="www.example.com"/>
+            <string-array name="security_endpoint" num="3">
+                <item value="url1"/>
+                <item value="url2"/>
+                <item value="url3"/>
+            </string-array>
+            <string-array name="first_party_endpoint" num="1">
+                <item value="url1"/>
+            </string-array>
+            <string-array name="service_provider_endpoint" num="2">
+                <item value="url55"/>
+                <item value="url56"/>
+            </string-array>
+            <string name="category" value="Food and drink"/>
+            <string name="email" value="max@maxloh.com"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 30333da..682adbc 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -82,13 +82,30 @@
     jarjar_rules: "jarjar-rules.txt",
 }
 
-// Host-side stub generator tool.
-java_binary_host {
-    name: "hoststubgen",
-    main_class: "com.android.hoststubgen.Main",
+// For sharing the code with other tools
+java_library_host {
+    name: "hoststubgen-lib",
+    defaults: ["ravenwood-internal-only-visibility-java"],
     srcs: ["src/**/*.kt"],
     static_libs: [
         "hoststubgen-helper-runtime",
+    ],
+    libs: [
+        "junit",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "ow2-asm-util",
+    ],
+}
+
+// Host-side stub generator tool.
+java_binary_host {
+    name: "hoststubgen",
+    main_class: "com.android.hoststubgen.HostStubGenMain",
+    static_libs: [
+        "hoststubgen-lib",
         "junit",
         "ow2-asm",
         "ow2-asm-analysis",
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 1089f82..803dc28 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -32,7 +32,6 @@
 import org.objectweb.asm.ClassReader
 import org.objectweb.asm.ClassVisitor
 import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.tree.ClassNode
 import org.objectweb.asm.util.CheckClassAdapter
 import java.io.BufferedInputStream
 import java.io.FileOutputStream
@@ -52,7 +51,7 @@
         val stats = HostStubGenStats()
 
         // Load all classes.
-        val allClasses = loadClassStructures(options.inJar.get)
+        val allClasses = ClassNodes.loadClassStructures(options.inJar.get)
 
         // Dump the classes, if specified.
         options.inputJarDumpFile.ifSet {
@@ -92,55 +91,6 @@
     }
 
     /**
-     * Load all the classes, without code.
-     */
-    private fun loadClassStructures(inJar: String): ClassNodes {
-        log.i("Reading class structure from $inJar ...")
-        val start = System.currentTimeMillis()
-
-        val allClasses = ClassNodes()
-
-        log.withIndent {
-            ZipFile(inJar).use { inZip ->
-                val inEntries = inZip.entries()
-
-                while (inEntries.hasMoreElements()) {
-                    val entry = inEntries.nextElement()
-
-                    BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                        if (entry.name.endsWith(".class")) {
-                            val cr = ClassReader(bis)
-                            val cn = ClassNode()
-                            cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
-                                    or ClassReader.SKIP_FRAMES)
-                            if (!allClasses.addClass(cn)) {
-                                log.w("Duplicate class found: ${cn.name}")
-                            }
-                        } else if (entry.name.endsWith(".dex")) {
-                            // Seems like it's an ART jar file. We can't process it.
-                            // It's a fatal error.
-                            throw InvalidJarFileException(
-                                    "$inJar is not a desktop jar file. It contains a *.dex file.")
-                        } else {
-                            // Unknown file type. Skip.
-                            while (bis.available() > 0) {
-                                bis.skip((1024 * 1024).toLong())
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        if (allClasses.size == 0) {
-            log.w("$inJar contains no *.class files.")
-        }
-
-        val end = System.currentTimeMillis()
-        log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
-        return allClasses
-    }
-
-    /**
      * Build the filter, which decides what classes/methods/fields should be put in stub or impl
      * jars, and "how". (e.g. with substitution?)
      */
@@ -229,7 +179,7 @@
         val intersectingJars = mutableMapOf<String, ClassNodes>()
 
         filenames.forEach { filename ->
-            intersectingJars[filename] = loadClassStructures(filename)
+            intersectingJars[filename] = ClassNodes.loadClassStructures(filename)
         }
         return intersectingJars
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
similarity index 86%
rename from tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
rename to tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 4882c00..45e7e30 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -13,18 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:JvmName("Main")
+@file:JvmName("HostStubGenMain")
 
 package com.android.hoststubgen
 
 import java.io.PrintWriter
 
-const val COMMAND_NAME = "HostStubGen"
-
 /**
  * Entry point.
  */
 fun main(args: Array<String>) {
+    executableName = "HostStubGen"
+
     var success = false
     var clanupOnError = false
 
@@ -33,7 +33,7 @@
         val options = HostStubGenOptions.parseArgs(args)
         clanupOnError = options.cleanUpOnError.get
 
-        log.v("HostStubGen started")
+        log.v("$executableName started")
         log.v("Options: $options")
 
         // Run.
@@ -41,7 +41,7 @@
 
         success = true
     } catch (e: Throwable) {
-        log.e("$COMMAND_NAME: Error: ${e.message}")
+        log.e("$executableName: Error: ${e.message}")
         if (e !is UserErrorException) {
             e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
         }
@@ -49,7 +49,7 @@
             TODO("Remove output jars here")
         }
     } finally {
-        log.i("$COMMAND_NAME finished")
+        log.i("$executableName finished")
         log.flush()
     }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 9f5d524..9ff798a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -268,7 +268,7 @@
             }
             if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) {
                 log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
-                        " $COMMAND_NAME will not generate jar files.")
+                        " $executableName will not generate jar files.")
             }
 
             if (ret.enableNonStubMethodCallDetection.get) {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 937e56c..aa63d8d9 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -16,6 +16,11 @@
 package com.android.hoststubgen
 
 /**
+ * Name of this executable. Set it in the main method.
+ */
+var executableName = "[command name not set]"
+
+/**
  * A regex that maches whitespate.
  */
 val whitespaceRegex = """\s+""".toRegex()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 0579c2b..83e122f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -34,6 +34,9 @@
 /** Descriptor of the class initializer method. */
 val CLASS_INITIALIZER_DESC = "()V"
 
+/** Name of constructors. */
+val CTOR_NAME = "<init>"
+
 /**
  * Find any of [anyAnnotations] from the list of visible / invisible annotations.
  */
@@ -135,10 +138,10 @@
         // Note, long and double will consume two local variable spaces, so the extra `i++`.
         when (type) {
             Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
-            Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+            Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
                 -> writer.visitVarInsn(Opcodes.ILOAD, i)
-            Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
             Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
+            Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
             Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
             else -> writer.visitVarInsn(Opcodes.ALOAD, i)
         }
@@ -154,10 +157,10 @@
         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
         when (type) {
             Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
-            Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+            Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
                 -> writer.visitInsn(Opcodes.IRETURN)
-            Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
             Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
+            Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
             Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
             else -> writer.visitInsn(Opcodes.ARETURN)
         }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index bc34ef0..92906a7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -16,13 +16,18 @@
 package com.android.hoststubgen.asm
 
 import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.InvalidJarFileException
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassReader
 import org.objectweb.asm.tree.AnnotationNode
 import org.objectweb.asm.tree.ClassNode
 import org.objectweb.asm.tree.FieldNode
 import org.objectweb.asm.tree.MethodNode
 import org.objectweb.asm.tree.TypeAnnotationNode
+import java.io.BufferedInputStream
 import java.io.PrintWriter
 import java.util.Arrays
+import java.util.zip.ZipFile
 
 /**
  * Stores all classes loaded from a jar file, in a form of [ClassNode]
@@ -62,8 +67,8 @@
 
     /** Find a field, which may not exist. */
     fun findField(
-            className: String,
-            fieldName: String,
+        className: String,
+        fieldName: String,
     ): FieldNode? {
         return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn ->
             return fn
@@ -72,14 +77,14 @@
 
     /** Find a method, which may not exist. */
     fun findMethod(
-            className: String,
-            methodName: String,
-            descriptor: String,
+        className: String,
+        methodName: String,
+        descriptor: String,
     ): MethodNode? {
         return findClass(className)?.methods
-                ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
-            return mn
-        }
+            ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+                return mn
+            }
     }
 
     /** @return true if a class has a class initializer. */
@@ -106,26 +111,33 @@
 
     private fun dumpClass(pw: PrintWriter, cn: ClassNode) {
         pw.printf("Class: %s [access: %x]\n", cn.name, cn.access)
-        dumpAnnotations(pw, "  ",
-                cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
-                cn.visibleAnnotations, cn.invisibleAnnotations,
-                )
+        dumpAnnotations(
+            pw, "  ",
+            cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
+            cn.visibleAnnotations, cn.invisibleAnnotations,
+        )
 
         for (f in cn.fields ?: emptyList()) {
-            pw.printf("  Field: %s [sig: %s] [desc: %s] [access: %x]\n",
-                    f.name, f.signature, f.desc, f.access)
-            dumpAnnotations(pw, "    ",
-                    f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
-                    f.visibleAnnotations, f.invisibleAnnotations,
-                    )
+            pw.printf(
+                "  Field: %s [sig: %s] [desc: %s] [access: %x]\n",
+                f.name, f.signature, f.desc, f.access
+            )
+            dumpAnnotations(
+                pw, "    ",
+                f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
+                f.visibleAnnotations, f.invisibleAnnotations,
+            )
         }
         for (m in cn.methods ?: emptyList()) {
-            pw.printf("  Method: %s [sig: %s] [desc: %s] [access: %x]\n",
-                    m.name, m.signature, m.desc, m.access)
-            dumpAnnotations(pw, "    ",
-                    m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
-                    m.visibleAnnotations, m.invisibleAnnotations,
-                    )
+            pw.printf(
+                "  Method: %s [sig: %s] [desc: %s] [access: %x]\n",
+                m.name, m.signature, m.desc, m.access
+            )
+            dumpAnnotations(
+                pw, "    ",
+                m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
+                m.visibleAnnotations, m.invisibleAnnotations,
+            )
         }
     }
 
@@ -136,7 +148,7 @@
         invisibleTypeAnnotations: List<TypeAnnotationNode>?,
         visibleAnnotations: List<AnnotationNode>?,
         invisibleAnnotations: List<AnnotationNode>?,
-        ) {
+    ) {
         for (an in visibleTypeAnnotations ?: emptyList()) {
             pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc)
         }
@@ -166,4 +178,55 @@
             }
         }
     }
+
+    companion object {
+        /**
+         * Load all the classes, without code.
+         */
+        fun loadClassStructures(inJar: String): ClassNodes {
+            log.i("Reading class structure from $inJar ...")
+            val start = System.currentTimeMillis()
+
+            val allClasses = ClassNodes()
+
+            log.withIndent {
+                ZipFile(inJar).use { inZip ->
+                    val inEntries = inZip.entries()
+
+                    while (inEntries.hasMoreElements()) {
+                        val entry = inEntries.nextElement()
+
+                        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+                            if (entry.name.endsWith(".class")) {
+                                val cr = ClassReader(bis)
+                                val cn = ClassNode()
+                                cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
+                                        or ClassReader.SKIP_FRAMES)
+                                if (!allClasses.addClass(cn)) {
+                                    log.w("Duplicate class found: ${cn.name}")
+                                }
+                            } else if (entry.name.endsWith(".dex")) {
+                                // Seems like it's an ART jar file. We can't process it.
+                                // It's a fatal error.
+                                throw InvalidJarFileException(
+                                    "$inJar is not a desktop jar file. It contains a *.dex file.")
+                            } else {
+                                // Unknown file type. Skip.
+                                while (bis.available() > 0) {
+                                    bis.skip((1024 * 1024).toLong())
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (allClasses.size == 0) {
+                log.w("$inJar contains no *.class files.")
+            }
+
+            val end = System.currentTimeMillis()
+            log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
+            return allClasses
+        }
+    }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 78b13fd..5a26fc6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -19,14 +19,14 @@
 import com.android.hoststubgen.HostStubGenInternalException
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
-import com.android.hoststubgen.asm.isAnonymousInnerClass
-import com.android.hoststubgen.log
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.isAnnotation
+import com.android.hoststubgen.asm.isAnonymousInnerClass
 import com.android.hoststubgen.asm.isAutoGeneratedEnumMember
 import com.android.hoststubgen.asm.isEnum
 import com.android.hoststubgen.asm.isSynthetic
 import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+import com.android.hoststubgen.log
 import org.objectweb.asm.tree.ClassNode
 
 /**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index f70a17d..fa8fe6c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1833,7 +1833,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 10, attributes: 2
+  interfaces: 0, fields: 1, methods: 11, attributes: 2
   int value;
     descriptor: I
     flags: (0x0000)
@@ -1938,6 +1938,10 @@
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
          x: athrow
       LineNumberTable:
+
+  public static native byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeInvisibleAnnotations:
@@ -1955,7 +1959,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 2
+  interfaces: 0, fields: 0, methods: 5, attributes: 2
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2013,6 +2017,22 @@
         Start  Length  Slot  Name   Signature
             0       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
             0       7     1   arg   I
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: i2b
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  arg1   B
+            0       5     1  arg2   B
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeInvisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 37de857..c605f76 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1554,7 +1554,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 9, attributes: 3
+  interfaces: 0, fields: 1, methods: 10, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -1686,6 +1686,15 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static native byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index c9c607c..11d5939 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -2236,7 +2236,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 10, attributes: 3
+  interfaces: 0, fields: 1, methods: 11, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -2435,6 +2435,23 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
@@ -2457,7 +2474,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 3
+  interfaces: 0, fields: 0, methods: 5, attributes: 3
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2551,6 +2568,31 @@
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeBytePlus
+         x: ldc           #x                 // String (BB)B
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iload_0
+        x: iload_1
+        x: iadd
+        x: i2b
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  arg1   B
+           15       5     1  arg2   B
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 37de857..c605f76 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1554,7 +1554,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 9, attributes: 3
+  interfaces: 0, fields: 1, methods: 10, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -1686,6 +1686,15 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static native byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index a57907d..088bc80 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -2743,7 +2743,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 11, attributes: 3
+  interfaces: 0, fields: 1, methods: 12, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -3002,6 +3002,28 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                // String nativeBytePlus
+         x: ldc           #x                // String (BB)B
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iload_1
+        x: invokestatic  #x                // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
@@ -3024,7 +3046,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  interfaces: 0, fields: 0, methods: 6, attributes: 3
   private static {};
     descriptor: ()V
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3148,6 +3170,36 @@
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeBytePlus
+         x: ldc           #x                 // String (BB)B
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+        x: ldc           #x                 // String nativeBytePlus
+        x: ldc           #x                 // String (BB)B
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: iload_0
+        x: iload_1
+        x: iadd
+        x: i2b
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           26       5     0  arg1   B
+           26       5     1  arg2   B
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index 5a5e22d..09ee183 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -52,4 +52,6 @@
     public static void nativeStillNotSupported_should_be_like_this() {
         throw new RuntimeException();
     }
+
+    public static native byte nativeBytePlus(byte arg1, byte arg2);
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
index 749ebaa..b23c216 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
@@ -34,4 +34,8 @@
     public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) {
         return source.value + arg;
     }
+
+    public static byte nativeBytePlus(byte arg1, byte arg2) {
+        return (byte) (arg1 + arg2);
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index ba17c75..762180d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -154,13 +154,22 @@
     }
 
     @Test
+    public void testNativeSubstitutionLong() {
+        assertThat(TinyFrameworkNative.nativeLongPlus(1L, 2L)).isEqualTo(3L);
+    }
+
+    @Test
+    public void testNativeSubstitutionByte() {
+        assertThat(TinyFrameworkNative.nativeBytePlus((byte) 3, (byte) 4)).isEqualTo(7);
+    }
+
+    @Test
     public void testNativeSubstitutionClass_nonStatic() {
         TinyFrameworkNative instance = new TinyFrameworkNative();
         instance.setValue(5);
         assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8);
     }
 
-
     @Test
     public void testSubstituteNativeWithThrow() throws Exception {
         // We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class,
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index 3c734bc..6c4e4c3 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -1,5 +1,4 @@
 package: "android.net.wifi.flags"
-container: "system"
 
 flag {
     name: "get_device_cross_akm_roaming_support"