Merge "Update cancel device state request to require foreground for cts compat" into main
diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
index 6363e9c..25e4c43 100644
--- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
@@ -16,7 +16,8 @@
 
 package android.libcore;
 
-import android.perftests.utils.BenchmarkState;
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
 import android.perftests.utils.PerfStatusReporter;
 
 import androidx.test.filters.LargeTest;
@@ -34,7 +35,8 @@
 @RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class SystemArrayCopyPerfTest {
-    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    @Rule
+    public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
 
     public static Collection<Object[]> getData() {
         return Arrays.asList(
@@ -51,7 +53,7 @@
         final int len = arrayLength;
         char[] src = new char[len];
         char[] dst = new char[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -63,7 +65,7 @@
         final int len = arrayLength;
         byte[] src = new byte[len];
         byte[] dst = new byte[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -75,7 +77,7 @@
         final int len = arrayLength;
         short[] src = new short[len];
         short[] dst = new short[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -87,7 +89,7 @@
         final int len = arrayLength;
         int[] src = new int[len];
         int[] dst = new int[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -99,7 +101,7 @@
         final int len = arrayLength;
         long[] src = new long[len];
         long[] dst = new long[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -111,7 +113,7 @@
         final int len = arrayLength;
         float[] src = new float[len];
         float[] dst = new float[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -123,7 +125,7 @@
         final int len = arrayLength;
         double[] src = new double[len];
         double[] dst = new double[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
@@ -135,7 +137,7 @@
         final int len = arrayLength;
         boolean[] src = new boolean[len];
         boolean[] dst = new boolean[len];
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
             System.arraycopy(src, 0, dst, 0, len);
         }
diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
index fbe67a4..c34936f 100644
--- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
@@ -19,6 +19,7 @@
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.RenderNode;
+import android.graphics.Typeface;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 
@@ -120,13 +121,34 @@
     public void testSetFontVariationSettings() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final Paint paint = new Paint(PAINT);
-        final Random random = new Random(0);
         while (state.keepRunning()) {
             state.pauseTiming();
-            int weight = random.nextInt(1000);
+            paint.setTypeface(null);
+            paint.setFontVariationSettings(null);
+            Typeface.clearTypefaceCachesForTestingPurpose();
             state.resumeTiming();
 
-            paint.setFontVariationSettings("'wght' " + weight);
+            paint.setFontVariationSettings("'wght' 450");
         }
+        Typeface.clearTypefaceCachesForTestingPurpose();
     }
+
+    @Test
+    public void testSetFontVariationSettings_Cached() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Paint paint = new Paint(PAINT);
+        Typeface.clearTypefaceCachesForTestingPurpose();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            paint.setTypeface(null);
+            paint.setFontVariationSettings(null);
+            state.resumeTiming();
+
+            paint.setFontVariationSettings("'wght' 450");
+        }
+
+        Typeface.clearTypefaceCachesForTestingPurpose();
+    }
+
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index c2aeada..c1894f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2629,8 +2629,13 @@
             for (int i=0; i<allowPowerExceptIdle.size(); i++) {
                 String pkg = allowPowerExceptIdle.valueAt(i);
                 try {
+                    // On some devices (eg. HSUM), some apps may
+                    // be not be pre-installed on user 0, but may be
+                    // pre-installed on FULL users. Look for pre-installed system
+                    // apps across all users to make sure they're properly
+                    // allowlisted.
                     ApplicationInfo ai = pm.getApplicationInfo(pkg,
-                            PackageManager.MATCH_SYSTEM_ONLY);
+                            PackageManager.MATCH_ANY_USER | PackageManager.MATCH_SYSTEM_ONLY);
                     int appid = UserHandle.getAppId(ai.uid);
                     mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                     mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
@@ -2641,8 +2646,13 @@
             for (int i=0; i<allowPower.size(); i++) {
                 String pkg = allowPower.valueAt(i);
                 try {
+                    // On some devices (eg. HSUM), some apps may
+                    // be not be pre-installed on user 0, but may be
+                    // pre-installed on FULL users. Look for pre-installed system
+                    // apps across all users to make sure they're properly
+                    // allowlisted.
                     ApplicationInfo ai = pm.getApplicationInfo(pkg,
-                            PackageManager.MATCH_SYSTEM_ONLY);
+                            PackageManager.MATCH_ANY_USER | PackageManager.MATCH_SYSTEM_ONLY);
                     int appid = UserHandle.getAppId(ai.uid);
                     // These apps are on both the whitelist-except-idle as well
                     // as the full whitelist, so they apply in all cases.
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 7353dde..a2d24f6 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -504,6 +504,9 @@
         public float dozeScreenBrightness;
         public int dozeScreenStateReason;
 
+        // Override that makes display use normal brightness while dozing.
+        public boolean useNormalBrightnessForDoze;
+
         public DisplayPowerRequest() {
             policy = POLICY_BRIGHT;
             useProximitySensor = false;
@@ -537,6 +540,7 @@
             dozeScreenBrightness = other.dozeScreenBrightness;
             dozeScreenState = other.dozeScreenState;
             dozeScreenStateReason = other.dozeScreenStateReason;
+            useNormalBrightnessForDoze = other.useNormalBrightnessForDoze;
         }
 
         @Override
@@ -561,7 +565,8 @@
                     && boostScreenBrightness == other.boostScreenBrightness
                     && floatEquals(dozeScreenBrightness, other.dozeScreenBrightness)
                     && dozeScreenState == other.dozeScreenState
-                    && dozeScreenStateReason == other.dozeScreenStateReason;
+                    && dozeScreenStateReason == other.dozeScreenStateReason
+                    && useNormalBrightnessForDoze == other.useNormalBrightnessForDoze;
         }
 
         private boolean floatEquals(float f1, float f2) {
@@ -587,7 +592,8 @@
                     + ", dozeScreenBrightness=" + dozeScreenBrightness
                     + ", dozeScreenState=" + Display.stateToString(dozeScreenState)
                     + ", dozeScreenStateReason="
-                            + Display.stateReasonToString(dozeScreenStateReason);
+                            + Display.stateReasonToString(dozeScreenStateReason)
+                    + ", useNormalBrightnessForDoze=" + useNormalBrightnessForDoze;
         }
 
         public static String policyToString(int policy) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 98e11375..83f2685 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,7 +25,7 @@
 import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.IKeyboardBacklightListener;
 import android.hardware.input.IKeyboardBacklightState;
-import android.hardware.input.IKeyboardSystemShortcutListener;
+import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.KeyboardLayoutSelectionResult;
@@ -241,13 +241,13 @@
 
     KeyGlyphMap getKeyGlyphMap(int deviceId);
 
-    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @EnforcePermission("MANAGE_KEY_GESTURES")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
-    void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+            + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+    void registerKeyGestureEventListener(IKeyGestureEventListener listener);
 
-    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @EnforcePermission("MANAGE_KEY_GESTURES")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
-    void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+            + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+    void unregisterKeyGestureEventListener(IKeyGestureEventListener listener);
 }
diff --git a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl b/core/java/android/hardware/input/IKeyGestureEventListener.aidl
similarity index 71%
rename from core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
rename to core/java/android/hardware/input/IKeyGestureEventListener.aidl
index 8d44917..2c430f1 100644
--- a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
+++ b/core/java/android/hardware/input/IKeyGestureEventListener.aidl
@@ -17,11 +17,10 @@
 package android.hardware.input;
 
 /** @hide */
-oneway interface IKeyboardSystemShortcutListener {
+oneway interface IKeyGestureEventListener {
 
     /**
-     * Called when the keyboard system shortcut is triggered.
+     * Called when a key gesture event occurs.
      */
-    void onKeyboardSystemShortcutTriggered(int deviceId, in int[] keycodes, int modifierState,
-                                           int shortcut);
+    void onKeyGestureEvent(int deviceId, in int[] keycodes, int modifierState, int shortcut);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6bc522b..04cfcd8 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1378,33 +1378,31 @@
     }
 
     /**
-     * Registers a keyboard system shortcut listener for {@link KeyboardSystemShortcut} being
-     * triggered.
+     * Registers a key gesture event listener for {@link KeyGestureEvent} being triggered.
      *
      * @param executor an executor on which the callback will be called
-     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @param listener the {@link KeyGestureEventListener}
      * @throws IllegalArgumentException if {@code listener} has already been registered previously.
      * @throws NullPointerException     if {@code listener} or {@code executor} is null.
      * @hide
-     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @see #unregisterKeyGestureEventListener(KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
-            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
-        mGlobal.registerKeyboardSystemShortcutListener(executor, listener);
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void registerKeyGestureEventListener(@NonNull Executor executor,
+            @NonNull KeyGestureEventListener listener) throws IllegalArgumentException {
+        mGlobal.registerKeyGestureEventListener(executor, listener);
     }
 
     /**
-     * Unregisters a previously added keyboard system shortcut listener.
+     * Unregisters a previously added key gesture event listener.
      *
-     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @param listener the {@link KeyGestureEventListener}
      * @hide
-     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+     * @see #registerKeyGestureEventListener(Executor, KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void unregisterKeyboardSystemShortcutListener(
-            @NonNull KeyboardSystemShortcutListener listener) {
-        mGlobal.unregisterKeyboardSystemShortcutListener(listener);
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void unregisterKeyGestureEventListener(@NonNull KeyGestureEventListener listener) {
+        mGlobal.unregisterKeyGestureEventListener(listener);
     }
 
     /**
@@ -1510,19 +1508,18 @@
     }
 
     /**
-     * A callback used to be notified about keyboard system shortcuts being triggered.
+     * A callback used to notify about key gesture event on completion.
      *
-     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
-     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @see #registerKeyGestureEventListener(Executor, KeyGestureEventListener)
+     * @see #unregisterKeyGestureEventListener(KeyGestureEventListener)
      * @hide
      */
-    public interface KeyboardSystemShortcutListener {
+    public interface KeyGestureEventListener {
         /**
-         * Called when a keyboard system shortcut is triggered.
+         * Called when a key gesture event occurs.
          *
-         * @param systemShortcut the shortcut info about the shortcut that was triggered.
+         * @param event the gesture event that occurred.
          */
-        void onKeyboardSystemShortcutTriggered(int deviceId,
-                @NonNull KeyboardSystemShortcut systemShortcut);
+        void onKeyGestureEvent(@NonNull KeyGestureEvent event);
     }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index f7fa557..03cf7c5 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -25,8 +25,8 @@
 import android.hardware.SensorManager;
 import android.hardware.input.InputManager.InputDeviceBatteryListener;
 import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.InputManager.KeyGestureEventListener;
 import android.hardware.input.InputManager.KeyboardBacklightListener;
-import android.hardware.input.InputManager.KeyboardSystemShortcutListener;
 import android.hardware.input.InputManager.OnTabletModeChangedListener;
 import android.hardware.input.InputManager.StickyModifierStateListener;
 import android.hardware.lights.Light;
@@ -111,13 +111,13 @@
     @Nullable
     private IStickyModifierStateListener mStickyModifierStateListener;
 
-    private final Object mKeyboardSystemShortcutListenerLock = new Object();
-    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    private final Object mKeyGestureEventListenerLock = new Object();
+    @GuardedBy("mKeyGestureEventListenerLock")
     @Nullable
-    private ArrayList<KeyboardSystemShortcutListenerDelegate> mKeyboardSystemShortcutListeners;
-    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    private ArrayList<KeyGestureEventListenerDelegate> mKeyGestureEventListeners;
+    @GuardedBy("mKeyGestureEventListenerLock")
     @Nullable
-    private IKeyboardSystemShortcutListener mKeyboardSystemShortcutListener;
+    private IKeyGestureEventListener mKeyGestureEventListener;
 
     // InputDeviceSensorManager gets notified synchronously from the binder thread when input
     // devices change, so it must be synchronized with the input device listeners.
@@ -1064,94 +1064,92 @@
         }
     }
 
-    private static final class KeyboardSystemShortcutListenerDelegate {
-        final KeyboardSystemShortcutListener mListener;
+    private static final class KeyGestureEventListenerDelegate {
+        final KeyGestureEventListener mListener;
         final Executor mExecutor;
 
-        KeyboardSystemShortcutListenerDelegate(KeyboardSystemShortcutListener listener,
+        KeyGestureEventListenerDelegate(KeyGestureEventListener listener,
                 Executor executor) {
             mListener = listener;
             mExecutor = executor;
         }
 
-        void onKeyboardSystemShortcutTriggered(int deviceId,
-                KeyboardSystemShortcut systemShortcut) {
-            mExecutor.execute(() ->
-                    mListener.onKeyboardSystemShortcutTriggered(deviceId, systemShortcut));
+        void onKeyGestureEvent(KeyGestureEvent event) {
+            mExecutor.execute(() -> mListener.onKeyGestureEvent(event));
         }
     }
 
-    private class LocalKeyboardSystemShortcutListener extends IKeyboardSystemShortcutListener.Stub {
+    private class LocalKeyGestureEventListener extends IKeyGestureEventListener.Stub {
 
         @Override
-        public void onKeyboardSystemShortcutTriggered(int deviceId, int[] keycodes,
-                int modifierState, int shortcut) {
-            synchronized (mKeyboardSystemShortcutListenerLock) {
-                if (mKeyboardSystemShortcutListeners == null) return;
-                final int numListeners = mKeyboardSystemShortcutListeners.size();
+        public void onKeyGestureEvent(int deviceId, int[] keycodes, int modifierState,
+                int gestureType) {
+            synchronized (mKeyGestureEventListenerLock) {
+                if (mKeyGestureEventListeners == null) return;
+                final int numListeners = mKeyGestureEventListeners.size();
                 for (int i = 0; i < numListeners; i++) {
-                    mKeyboardSystemShortcutListeners.get(i)
-                            .onKeyboardSystemShortcutTriggered(deviceId,
-                                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut));
+                    mKeyGestureEventListeners.get(i)
+                            .onKeyGestureEvent(
+                                    new KeyGestureEvent(deviceId, keycodes, modifierState,
+                                            gestureType));
                 }
             }
         }
     }
 
     /**
-     * @see InputManager#registerKeyboardSystemShortcutListener(Executor,
-     * KeyboardSystemShortcutListener)
+     * @see InputManager#registerKeyGestureEventListener(Executor,
+     * KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
-            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    void registerKeyGestureEventListener(@NonNull Executor executor,
+            @NonNull KeyGestureEventListener listener) throws IllegalArgumentException {
         Objects.requireNonNull(executor, "executor should not be null");
         Objects.requireNonNull(listener, "listener should not be null");
 
-        synchronized (mKeyboardSystemShortcutListenerLock) {
-            if (mKeyboardSystemShortcutListener == null) {
-                mKeyboardSystemShortcutListeners = new ArrayList<>();
-                mKeyboardSystemShortcutListener = new LocalKeyboardSystemShortcutListener();
+        synchronized (mKeyGestureEventListenerLock) {
+            if (mKeyGestureEventListener == null) {
+                mKeyGestureEventListeners = new ArrayList<>();
+                mKeyGestureEventListener = new LocalKeyGestureEventListener();
 
                 try {
-                    mIm.registerKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                    mIm.registerKeyGestureEventListener(mKeyGestureEventListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
             }
-            final int numListeners = mKeyboardSystemShortcutListeners.size();
+            final int numListeners = mKeyGestureEventListeners.size();
             for (int i = 0; i < numListeners; i++) {
-                if (mKeyboardSystemShortcutListeners.get(i).mListener == listener) {
+                if (mKeyGestureEventListeners.get(i).mListener == listener) {
                     throw new IllegalArgumentException("Listener has already been registered!");
                 }
             }
-            KeyboardSystemShortcutListenerDelegate delegate =
-                    new KeyboardSystemShortcutListenerDelegate(listener, executor);
-            mKeyboardSystemShortcutListeners.add(delegate);
+            KeyGestureEventListenerDelegate delegate =
+                    new KeyGestureEventListenerDelegate(listener, executor);
+            mKeyGestureEventListeners.add(delegate);
         }
     }
 
     /**
-     * @see InputManager#unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @see InputManager#unregisterKeyGestureEventListener(KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    void unregisterKeyboardSystemShortcutListener(
-            @NonNull KeyboardSystemShortcutListener listener) {
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    void unregisterKeyGestureEventListener(@NonNull KeyGestureEventListener listener) {
         Objects.requireNonNull(listener, "listener should not be null");
 
-        synchronized (mKeyboardSystemShortcutListenerLock) {
-            if (mKeyboardSystemShortcutListeners == null) {
+        synchronized (mKeyGestureEventListenerLock) {
+            if (mKeyGestureEventListeners == null) {
                 return;
             }
-            mKeyboardSystemShortcutListeners.removeIf((delegate) -> delegate.mListener == listener);
-            if (mKeyboardSystemShortcutListeners.isEmpty()) {
+            mKeyGestureEventListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mKeyGestureEventListeners.isEmpty()) {
                 try {
-                    mIm.unregisterKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                    mIm.unregisterKeyGestureEventListener(mKeyGestureEventListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
-                mKeyboardSystemShortcutListeners = null;
-                mKeyboardSystemShortcutListener = null;
+                mKeyGestureEventListeners = null;
+                mKeyGestureEventListener = null;
             }
         }
     }
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
new file mode 100644
index 0000000..7a8dd33
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright 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.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the keyboard gesture event being triggered by an external keyboard.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class KeyGestureEvent {
+
+    private final int mDeviceId;
+    @NonNull
+    private final int[] mKeycodes;
+    private final int mModifierState;
+    @KeyGestureType
+    private final int mKeyGestureType;
+
+
+    public static final int KEY_GESTURE_TYPE_UNSPECIFIED =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+    public static final int KEY_GESTURE_TYPE_HOME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
+    public static final int KEY_GESTURE_TYPE_RECENT_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
+    public static final int KEY_GESTURE_TYPE_BACK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
+    public static final int KEY_GESTURE_TYPE_APP_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_TASKBAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
+    public static final int KEY_GESTURE_TYPE_TAKE_SCREENSHOT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
+    public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
+    public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
+    public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
+    public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
+    public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
+    public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
+    public static final int KEY_GESTURE_TYPE_VOLUME_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
+    public static final int KEY_GESTURE_TYPE_VOLUME_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
+    public static final int KEY_GESTURE_TYPE_VOLUME_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
+    public static final int KEY_GESTURE_TYPE_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
+    public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
+    public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
+    public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
+    public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
+    public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+    public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
+    public static final int KEY_GESTURE_TYPE_LOCK_SCREEN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
+    public static final int KEY_GESTURE_TYPE_OPEN_NOTES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_POWER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
+    public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
+    public static final int KEY_GESTURE_TYPE_SLEEP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
+    public static final int KEY_GESTURE_TYPE_WAKEUP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
+    public static final int KEY_GESTURE_TYPE_MEDIA_KEY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+    public static final int KEY_GESTURE_TYPE_DESKTOP_MODE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
+    public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyGestureEvent.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @IntDef(prefix = "KEY_GESTURE_TYPE_", value = {
+        KEY_GESTURE_TYPE_UNSPECIFIED,
+        KEY_GESTURE_TYPE_HOME,
+        KEY_GESTURE_TYPE_RECENT_APPS,
+        KEY_GESTURE_TYPE_BACK,
+        KEY_GESTURE_TYPE_APP_SWITCH,
+        KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+        KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
+        KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+        KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+        KEY_GESTURE_TYPE_TOGGLE_TASKBAR,
+        KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+        KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+        KEY_GESTURE_TYPE_BRIGHTNESS_UP,
+        KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+        KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+        KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+        KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+        KEY_GESTURE_TYPE_VOLUME_UP,
+        KEY_GESTURE_TYPE_VOLUME_DOWN,
+        KEY_GESTURE_TYPE_VOLUME_MUTE,
+        KEY_GESTURE_TYPE_ALL_APPS,
+        KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+        KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+        KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+        KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+        KEY_GESTURE_TYPE_SYSTEM_MUTE,
+        KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
+        KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS,
+        KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+        KEY_GESTURE_TYPE_LOCK_SCREEN,
+        KEY_GESTURE_TYPE_OPEN_NOTES,
+        KEY_GESTURE_TYPE_TOGGLE_POWER,
+        KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
+        KEY_GESTURE_TYPE_SLEEP,
+        KEY_GESTURE_TYPE_WAKEUP,
+        KEY_GESTURE_TYPE_MEDIA_KEY,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
+        KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+        KEY_GESTURE_TYPE_DESKTOP_MODE,
+        KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface KeyGestureType {}
+
+    @DataClass.Generated.Member
+    public static String keyGestureTypeToString(@KeyGestureType int value) {
+        switch (value) {
+            case KEY_GESTURE_TYPE_UNSPECIFIED:
+                    return "KEY_GESTURE_TYPE_UNSPECIFIED";
+            case KEY_GESTURE_TYPE_HOME:
+                    return "KEY_GESTURE_TYPE_HOME";
+            case KEY_GESTURE_TYPE_RECENT_APPS:
+                    return "KEY_GESTURE_TYPE_RECENT_APPS";
+            case KEY_GESTURE_TYPE_BACK:
+                    return "KEY_GESTURE_TYPE_BACK";
+            case KEY_GESTURE_TYPE_APP_SWITCH:
+                    return "KEY_GESTURE_TYPE_APP_SWITCH";
+            case KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+                    return "KEY_GESTURE_TYPE_LAUNCH_ASSISTANT";
+            case KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
+                    return "KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT";
+            case KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS";
+            case KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL:
+                    return "KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL";
+            case KEY_GESTURE_TYPE_TOGGLE_TASKBAR:
+                    return "KEY_GESTURE_TYPE_TOGGLE_TASKBAR";
+            case KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
+                    return "KEY_GESTURE_TYPE_TAKE_SCREENSHOT";
+            case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
+                    return "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER";
+            case KEY_GESTURE_TYPE_BRIGHTNESS_UP:
+                    return "KEY_GESTURE_TYPE_BRIGHTNESS_UP";
+            case KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
+                    return "KEY_GESTURE_TYPE_BRIGHTNESS_DOWN";
+            case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+                    return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP";
+            case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+                    return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN";
+            case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+                    return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE";
+            case KEY_GESTURE_TYPE_VOLUME_UP:
+                    return "KEY_GESTURE_TYPE_VOLUME_UP";
+            case KEY_GESTURE_TYPE_VOLUME_DOWN:
+                    return "KEY_GESTURE_TYPE_VOLUME_DOWN";
+            case KEY_GESTURE_TYPE_VOLUME_MUTE:
+                    return "KEY_GESTURE_TYPE_VOLUME_MUTE";
+            case KEY_GESTURE_TYPE_ALL_APPS:
+                    return "KEY_GESTURE_TYPE_ALL_APPS";
+            case KEY_GESTURE_TYPE_LAUNCH_SEARCH:
+                    return "KEY_GESTURE_TYPE_LAUNCH_SEARCH";
+            case KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
+                    return "KEY_GESTURE_TYPE_LANGUAGE_SWITCH";
+            case KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
+                    return "KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS";
+            case KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+                    return "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK";
+            case KEY_GESTURE_TYPE_SYSTEM_MUTE:
+                    return "KEY_GESTURE_TYPE_SYSTEM_MUTE";
+            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION:
+                    return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION";
+            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS:
+                    return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS";
+            case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
+                    return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT";
+            case KEY_GESTURE_TYPE_LOCK_SCREEN:
+                    return "KEY_GESTURE_TYPE_LOCK_SCREEN";
+            case KEY_GESTURE_TYPE_OPEN_NOTES:
+                    return "KEY_GESTURE_TYPE_OPEN_NOTES";
+            case KEY_GESTURE_TYPE_TOGGLE_POWER:
+                    return "KEY_GESTURE_TYPE_TOGGLE_POWER";
+            case KEY_GESTURE_TYPE_SYSTEM_NAVIGATION:
+                    return "KEY_GESTURE_TYPE_SYSTEM_NAVIGATION";
+            case KEY_GESTURE_TYPE_SLEEP:
+                    return "KEY_GESTURE_TYPE_SLEEP";
+            case KEY_GESTURE_TYPE_WAKEUP:
+                    return "KEY_GESTURE_TYPE_WAKEUP";
+            case KEY_GESTURE_TYPE_MEDIA_KEY:
+                    return "KEY_GESTURE_TYPE_MEDIA_KEY";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS";
+            case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+                    return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+            case KEY_GESTURE_TYPE_DESKTOP_MODE:
+                    return "KEY_GESTURE_TYPE_DESKTOP_MODE";
+            case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
+                    return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    public KeyGestureEvent(
+            int deviceId,
+            @NonNull int[] keycodes,
+            int modifierState,
+            @KeyGestureType int keyGestureType) {
+        this.mDeviceId = deviceId;
+        this.mKeycodes = keycodes;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mKeycodes);
+        this.mModifierState = modifierState;
+        this.mKeyGestureType = keyGestureType;
+
+        if (!(mKeyGestureType == KEY_GESTURE_TYPE_UNSPECIFIED)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_HOME)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_RECENT_APPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_BACK)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_APP_SWITCH)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_ASSISTANT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_TASKBAR)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TAKE_SCREENSHOT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_BRIGHTNESS_UP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_BRIGHTNESS_DOWN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_UP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_DOWN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_MUTE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_ALL_APPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_SEARCH)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LANGUAGE_SWITCH)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SYSTEM_MUTE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LOCK_SCREEN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_OPEN_NOTES)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_POWER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SYSTEM_NAVIGATION)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SLEEP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_WAKEUP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_MEDIA_KEY)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_DESKTOP_MODE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION)) {
+            throw new java.lang.IllegalArgumentException(
+                    "keyGestureType was " + mKeyGestureType + " but must be one of: "
+                            + "KEY_GESTURE_TYPE_UNSPECIFIED(" + KEY_GESTURE_TYPE_UNSPECIFIED + "), "
+                            + "KEY_GESTURE_TYPE_HOME(" + KEY_GESTURE_TYPE_HOME + "), "
+                            + "KEY_GESTURE_TYPE_RECENT_APPS(" + KEY_GESTURE_TYPE_RECENT_APPS + "), "
+                            + "KEY_GESTURE_TYPE_BACK(" + KEY_GESTURE_TYPE_BACK + "), "
+                            + "KEY_GESTURE_TYPE_APP_SWITCH(" + KEY_GESTURE_TYPE_APP_SWITCH + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_ASSISTANT(" + KEY_GESTURE_TYPE_LAUNCH_ASSISTANT + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT(" + KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS(" + KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL(" + KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_TASKBAR(" + KEY_GESTURE_TYPE_TOGGLE_TASKBAR + "), "
+                            + "KEY_GESTURE_TYPE_TAKE_SCREENSHOT(" + KEY_GESTURE_TYPE_TAKE_SCREENSHOT + "), "
+                            + "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER(" + KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER + "), "
+                            + "KEY_GESTURE_TYPE_BRIGHTNESS_UP(" + KEY_GESTURE_TYPE_BRIGHTNESS_UP + "), "
+                            + "KEY_GESTURE_TYPE_BRIGHTNESS_DOWN(" + KEY_GESTURE_TYPE_BRIGHTNESS_DOWN + "), "
+                            + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP + "), "
+                            + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN + "), "
+                            + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE + "), "
+                            + "KEY_GESTURE_TYPE_VOLUME_UP(" + KEY_GESTURE_TYPE_VOLUME_UP + "), "
+                            + "KEY_GESTURE_TYPE_VOLUME_DOWN(" + KEY_GESTURE_TYPE_VOLUME_DOWN + "), "
+                            + "KEY_GESTURE_TYPE_VOLUME_MUTE(" + KEY_GESTURE_TYPE_VOLUME_MUTE + "), "
+                            + "KEY_GESTURE_TYPE_ALL_APPS(" + KEY_GESTURE_TYPE_ALL_APPS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_SEARCH(" + KEY_GESTURE_TYPE_LAUNCH_SEARCH + "), "
+                            + "KEY_GESTURE_TYPE_LANGUAGE_SWITCH(" + KEY_GESTURE_TYPE_LANGUAGE_SWITCH + "), "
+                            + "KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS(" + KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK(" + KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK + "), "
+                            + "KEY_GESTURE_TYPE_SYSTEM_MUTE(" + KEY_GESTURE_TYPE_SYSTEM_MUTE + "), "
+                            + "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION(" + KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION + "), "
+                            + "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS(" + KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS + "), "
+                            + "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT(" + KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT + "), "
+                            + "KEY_GESTURE_TYPE_LOCK_SCREEN(" + KEY_GESTURE_TYPE_LOCK_SCREEN + "), "
+                            + "KEY_GESTURE_TYPE_OPEN_NOTES(" + KEY_GESTURE_TYPE_OPEN_NOTES + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_POWER(" + KEY_GESTURE_TYPE_TOGGLE_POWER + "), "
+                            + "KEY_GESTURE_TYPE_SYSTEM_NAVIGATION(" + KEY_GESTURE_TYPE_SYSTEM_NAVIGATION + "), "
+                            + "KEY_GESTURE_TYPE_SLEEP(" + KEY_GESTURE_TYPE_SLEEP + "), "
+                            + "KEY_GESTURE_TYPE_WAKEUP(" + KEY_GESTURE_TYPE_WAKEUP + "), "
+                            + "KEY_GESTURE_TYPE_MEDIA_KEY(" + KEY_GESTURE_TYPE_MEDIA_KEY + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
+                            + "KEY_GESTURE_TYPE_DESKTOP_MODE(" + KEY_GESTURE_TYPE_DESKTOP_MODE + "), "
+                            + "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION(" + KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public int getDeviceId() {
+        return mDeviceId;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull int[] getKeycodes() {
+        return mKeycodes;
+    }
+
+    @DataClass.Generated.Member
+    public int getModifierState() {
+        return mModifierState;
+    }
+
+    @DataClass.Generated.Member
+    public @KeyGestureType int getKeyGestureType() {
+        return mKeyGestureType;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyGestureEvent { " +
+                "deviceId = " + mDeviceId + ", " +
+                "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
+                "modifierState = " + mModifierState + ", " +
+                "keyGestureType = " + keyGestureTypeToString(mKeyGestureType) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyGestureEvent other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyGestureEvent that = (KeyGestureEvent) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mDeviceId == that.mDeviceId
+                && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
+                && mModifierState == that.mModifierState
+                && mKeyGestureType == that.mKeyGestureType;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mDeviceId;
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
+        _hash = 31 * _hash + mModifierState;
+        _hash = 31 * _hash + mKeyGestureType;
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1723409092192L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyGestureEvent.java",
+            inputSignatures = "private final  int mDeviceId\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final  int mModifierState\nprivate final @android.hardware.input.KeyGestureEvent.KeyGestureType int mKeyGestureType\npublic static final  int KEY_GESTURE_TYPE_UNSPECIFIED\npublic static final  int KEY_GESTURE_TYPE_HOME\npublic static final  int KEY_GESTURE_TYPE_RECENT_APPS\npublic static final  int KEY_GESTURE_TYPE_BACK\npublic static final  int KEY_GESTURE_TYPE_APP_SWITCH\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_ASSISTANT\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_TASKBAR\npublic static final  int KEY_GESTURE_TYPE_TAKE_SCREENSHOT\npublic static final  int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER\npublic static final  int KEY_GESTURE_TYPE_BRIGHTNESS_UP\npublic static final  int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN\npublic static final  int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP\npublic static final  int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN\npublic static final  int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final  int KEY_GESTURE_TYPE_VOLUME_UP\npublic static final  int KEY_GESTURE_TYPE_VOLUME_DOWN\npublic static final  int KEY_GESTURE_TYPE_VOLUME_MUTE\npublic static final  int KEY_GESTURE_TYPE_ALL_APPS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_SEARCH\npublic static final  int KEY_GESTURE_TYPE_LANGUAGE_SWITCH\npublic static final  int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK\npublic static final  int KEY_GESTURE_TYPE_SYSTEM_MUTE\npublic static final  int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION\npublic static final  int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS\npublic static final  int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT\npublic static final  int KEY_GESTURE_TYPE_LOCK_SCREEN\npublic static final  int KEY_GESTURE_TYPE_OPEN_NOTES\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_POWER\npublic static final  int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION\npublic static final  int KEY_GESTURE_TYPE_SLEEP\npublic static final  int KEY_GESTURE_TYPE_WAKEUP\npublic static final  int KEY_GESTURE_TYPE_MEDIA_KEY\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final  int KEY_GESTURE_TYPE_DESKTOP_MODE\npublic static final  int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION\nclass KeyGestureEvent extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/hardware/input/KeyboardSystemShortcut.java b/core/java/android/hardware/input/KeyboardSystemShortcut.java
deleted file mode 100644
index 89cf877..0000000
--- a/core/java/android/hardware/input/KeyboardSystemShortcut.java
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright 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.hardware.input;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Provides information about the keyboard shortcut being triggered by an external keyboard.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public class KeyboardSystemShortcut {
-
-    private static final String TAG = "KeyboardSystemShortcut";
-
-    @NonNull
-    private final int[] mKeycodes;
-    private final int mModifierState;
-    @SystemShortcut
-    private final int mSystemShortcut;
-
-
-    public static final int SYSTEM_SHORTCUT_UNSPECIFIED =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
-    public static final int SYSTEM_SHORTCUT_HOME =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
-    public static final int SYSTEM_SHORTCUT_RECENT_APPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
-    public static final int SYSTEM_SHORTCUT_BACK =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
-    public static final int SYSTEM_SHORTCUT_APP_SWITCH =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
-    public static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
-    public static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
-    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
-    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
-    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
-    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
-    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
-    public static final int SYSTEM_SHORTCUT_VOLUME_UP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
-    public static final int SYSTEM_SHORTCUT_VOLUME_DOWN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
-    public static final int SYSTEM_SHORTCUT_VOLUME_MUTE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
-    public static final int SYSTEM_SHORTCUT_ALL_APPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
-    public static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
-    public static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
-    public static final int SYSTEM_SHORTCUT_SYSTEM_MUTE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
-    public static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
-    public static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
-    public static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
-    public static final int SYSTEM_SHORTCUT_LOCK_SCREEN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
-    public static final int SYSTEM_SHORTCUT_OPEN_NOTES =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_POWER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
-    public static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
-    public static final int SYSTEM_SHORTCUT_SLEEP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
-    public static final int SYSTEM_SHORTCUT_WAKEUP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
-    public static final int SYSTEM_SHORTCUT_MEDIA_KEY =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
-    public static final int SYSTEM_SHORTCUT_DESKTOP_MODE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
-    public static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @IntDef(prefix = "SYSTEM_SHORTCUT_", value = {
-        SYSTEM_SHORTCUT_UNSPECIFIED,
-        SYSTEM_SHORTCUT_HOME,
-        SYSTEM_SHORTCUT_RECENT_APPS,
-        SYSTEM_SHORTCUT_BACK,
-        SYSTEM_SHORTCUT_APP_SWITCH,
-        SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
-        SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
-        SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
-        SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
-        SYSTEM_SHORTCUT_TOGGLE_TASKBAR,
-        SYSTEM_SHORTCUT_TAKE_SCREENSHOT,
-        SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
-        SYSTEM_SHORTCUT_BRIGHTNESS_UP,
-        SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
-        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
-        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
-        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
-        SYSTEM_SHORTCUT_VOLUME_UP,
-        SYSTEM_SHORTCUT_VOLUME_DOWN,
-        SYSTEM_SHORTCUT_VOLUME_MUTE,
-        SYSTEM_SHORTCUT_ALL_APPS,
-        SYSTEM_SHORTCUT_LAUNCH_SEARCH,
-        SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
-        SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
-        SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
-        SYSTEM_SHORTCUT_SYSTEM_MUTE,
-        SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
-        SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS,
-        SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT,
-        SYSTEM_SHORTCUT_LOCK_SCREEN,
-        SYSTEM_SHORTCUT_OPEN_NOTES,
-        SYSTEM_SHORTCUT_TOGGLE_POWER,
-        SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
-        SYSTEM_SHORTCUT_SLEEP,
-        SYSTEM_SHORTCUT_WAKEUP,
-        SYSTEM_SHORTCUT_MEDIA_KEY,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS,
-        SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
-        SYSTEM_SHORTCUT_DESKTOP_MODE,
-        SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @DataClass.Generated.Member
-    public @interface SystemShortcut {}
-
-    @DataClass.Generated.Member
-    public static String systemShortcutToString(@SystemShortcut int value) {
-        switch (value) {
-            case SYSTEM_SHORTCUT_UNSPECIFIED:
-                    return "SYSTEM_SHORTCUT_UNSPECIFIED";
-            case SYSTEM_SHORTCUT_HOME:
-                    return "SYSTEM_SHORTCUT_HOME";
-            case SYSTEM_SHORTCUT_RECENT_APPS:
-                    return "SYSTEM_SHORTCUT_RECENT_APPS";
-            case SYSTEM_SHORTCUT_BACK:
-                    return "SYSTEM_SHORTCUT_BACK";
-            case SYSTEM_SHORTCUT_APP_SWITCH:
-                    return "SYSTEM_SHORTCUT_APP_SWITCH";
-            case SYSTEM_SHORTCUT_LAUNCH_ASSISTANT:
-                    return "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT";
-            case SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT:
-                    return "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT";
-            case SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS";
-            case SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL:
-                    return "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL";
-            case SYSTEM_SHORTCUT_TOGGLE_TASKBAR:
-                    return "SYSTEM_SHORTCUT_TOGGLE_TASKBAR";
-            case SYSTEM_SHORTCUT_TAKE_SCREENSHOT:
-                    return "SYSTEM_SHORTCUT_TAKE_SCREENSHOT";
-            case SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER:
-                    return "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER";
-            case SYSTEM_SHORTCUT_BRIGHTNESS_UP:
-                    return "SYSTEM_SHORTCUT_BRIGHTNESS_UP";
-            case SYSTEM_SHORTCUT_BRIGHTNESS_DOWN:
-                    return "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN";
-            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP:
-                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP";
-            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN:
-                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN";
-            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE:
-                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE";
-            case SYSTEM_SHORTCUT_VOLUME_UP:
-                    return "SYSTEM_SHORTCUT_VOLUME_UP";
-            case SYSTEM_SHORTCUT_VOLUME_DOWN:
-                    return "SYSTEM_SHORTCUT_VOLUME_DOWN";
-            case SYSTEM_SHORTCUT_VOLUME_MUTE:
-                    return "SYSTEM_SHORTCUT_VOLUME_MUTE";
-            case SYSTEM_SHORTCUT_ALL_APPS:
-                    return "SYSTEM_SHORTCUT_ALL_APPS";
-            case SYSTEM_SHORTCUT_LAUNCH_SEARCH:
-                    return "SYSTEM_SHORTCUT_LAUNCH_SEARCH";
-            case SYSTEM_SHORTCUT_LANGUAGE_SWITCH:
-                    return "SYSTEM_SHORTCUT_LANGUAGE_SWITCH";
-            case SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS:
-                    return "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS";
-            case SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK:
-                    return "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK";
-            case SYSTEM_SHORTCUT_SYSTEM_MUTE:
-                    return "SYSTEM_SHORTCUT_SYSTEM_MUTE";
-            case SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION:
-                    return "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION";
-            case SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS:
-                    return "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS";
-            case SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT:
-                    return "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT";
-            case SYSTEM_SHORTCUT_LOCK_SCREEN:
-                    return "SYSTEM_SHORTCUT_LOCK_SCREEN";
-            case SYSTEM_SHORTCUT_OPEN_NOTES:
-                    return "SYSTEM_SHORTCUT_OPEN_NOTES";
-            case SYSTEM_SHORTCUT_TOGGLE_POWER:
-                    return "SYSTEM_SHORTCUT_TOGGLE_POWER";
-            case SYSTEM_SHORTCUT_SYSTEM_NAVIGATION:
-                    return "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION";
-            case SYSTEM_SHORTCUT_SLEEP:
-                    return "SYSTEM_SHORTCUT_SLEEP";
-            case SYSTEM_SHORTCUT_WAKEUP:
-                    return "SYSTEM_SHORTCUT_WAKEUP";
-            case SYSTEM_SHORTCUT_MEDIA_KEY:
-                    return "SYSTEM_SHORTCUT_MEDIA_KEY";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS";
-            case SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
-                    return "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
-            case SYSTEM_SHORTCUT_DESKTOP_MODE:
-                    return "SYSTEM_SHORTCUT_DESKTOP_MODE";
-            case SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION:
-                    return "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION";
-            default: return Integer.toHexString(value);
-        }
-    }
-
-    @DataClass.Generated.Member
-    public KeyboardSystemShortcut(
-            @NonNull int[] keycodes,
-            int modifierState,
-            @SystemShortcut int systemShortcut) {
-        this.mKeycodes = keycodes;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mKeycodes);
-        this.mModifierState = modifierState;
-        this.mSystemShortcut = systemShortcut;
-
-        if (!(mSystemShortcut == SYSTEM_SHORTCUT_UNSPECIFIED)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_HOME)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_RECENT_APPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_BACK)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_APP_SWITCH)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_ASSISTANT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_TASKBAR)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TAKE_SCREENSHOT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_UP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_DOWN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_UP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_DOWN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_MUTE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_ALL_APPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SEARCH)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LANGUAGE_SWITCH)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_MUTE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LOCK_SCREEN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_NOTES)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_POWER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_NAVIGATION)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SLEEP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_WAKEUP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_MEDIA_KEY)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_DESKTOP_MODE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION)) {
-            throw new java.lang.IllegalArgumentException(
-                    "systemShortcut was " + mSystemShortcut + " but must be one of: "
-                            + "SYSTEM_SHORTCUT_UNSPECIFIED(" + SYSTEM_SHORTCUT_UNSPECIFIED + "), "
-                            + "SYSTEM_SHORTCUT_HOME(" + SYSTEM_SHORTCUT_HOME + "), "
-                            + "SYSTEM_SHORTCUT_RECENT_APPS(" + SYSTEM_SHORTCUT_RECENT_APPS + "), "
-                            + "SYSTEM_SHORTCUT_BACK(" + SYSTEM_SHORTCUT_BACK + "), "
-                            + "SYSTEM_SHORTCUT_APP_SWITCH(" + SYSTEM_SHORTCUT_APP_SWITCH + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_ASSISTANT + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS(" + SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL(" + SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_TASKBAR(" + SYSTEM_SHORTCUT_TOGGLE_TASKBAR + "), "
-                            + "SYSTEM_SHORTCUT_TAKE_SCREENSHOT(" + SYSTEM_SHORTCUT_TAKE_SCREENSHOT + "), "
-                            + "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER(" + SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER + "), "
-                            + "SYSTEM_SHORTCUT_BRIGHTNESS_UP(" + SYSTEM_SHORTCUT_BRIGHTNESS_UP + "), "
-                            + "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN(" + SYSTEM_SHORTCUT_BRIGHTNESS_DOWN + "), "
-                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP + "), "
-                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN + "), "
-                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE + "), "
-                            + "SYSTEM_SHORTCUT_VOLUME_UP(" + SYSTEM_SHORTCUT_VOLUME_UP + "), "
-                            + "SYSTEM_SHORTCUT_VOLUME_DOWN(" + SYSTEM_SHORTCUT_VOLUME_DOWN + "), "
-                            + "SYSTEM_SHORTCUT_VOLUME_MUTE(" + SYSTEM_SHORTCUT_VOLUME_MUTE + "), "
-                            + "SYSTEM_SHORTCUT_ALL_APPS(" + SYSTEM_SHORTCUT_ALL_APPS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_SEARCH(" + SYSTEM_SHORTCUT_LAUNCH_SEARCH + "), "
-                            + "SYSTEM_SHORTCUT_LANGUAGE_SWITCH(" + SYSTEM_SHORTCUT_LANGUAGE_SWITCH + "), "
-                            + "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS(" + SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK(" + SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK + "), "
-                            + "SYSTEM_SHORTCUT_SYSTEM_MUTE(" + SYSTEM_SHORTCUT_SYSTEM_MUTE + "), "
-                            + "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION(" + SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION + "), "
-                            + "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS(" + SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS + "), "
-                            + "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT(" + SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT + "), "
-                            + "SYSTEM_SHORTCUT_LOCK_SCREEN(" + SYSTEM_SHORTCUT_LOCK_SCREEN + "), "
-                            + "SYSTEM_SHORTCUT_OPEN_NOTES(" + SYSTEM_SHORTCUT_OPEN_NOTES + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_POWER(" + SYSTEM_SHORTCUT_TOGGLE_POWER + "), "
-                            + "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION(" + SYSTEM_SHORTCUT_SYSTEM_NAVIGATION + "), "
-                            + "SYSTEM_SHORTCUT_SLEEP(" + SYSTEM_SHORTCUT_SLEEP + "), "
-                            + "SYSTEM_SHORTCUT_WAKEUP(" + SYSTEM_SHORTCUT_WAKEUP + "), "
-                            + "SYSTEM_SHORTCUT_MEDIA_KEY(" + SYSTEM_SHORTCUT_MEDIA_KEY + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
-                            + "SYSTEM_SHORTCUT_DESKTOP_MODE(" + SYSTEM_SHORTCUT_DESKTOP_MODE + "), "
-                            + "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION(" + SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION + ")");
-        }
-
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull int[] getKeycodes() {
-        return mKeycodes;
-    }
-
-    @DataClass.Generated.Member
-    public int getModifierState() {
-        return mModifierState;
-    }
-
-    @DataClass.Generated.Member
-    public @SystemShortcut int getSystemShortcut() {
-        return mSystemShortcut;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "KeyboardSystemShortcut { " +
-                "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
-                "modifierState = " + mModifierState + ", " +
-                "systemShortcut = " + systemShortcutToString(mSystemShortcut) +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(KeyboardSystemShortcut other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        KeyboardSystemShortcut that = (KeyboardSystemShortcut) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
-                && mModifierState == that.mModifierState
-                && mSystemShortcut == that.mSystemShortcut;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
-        _hash = 31 * _hash + mModifierState;
-        _hash = 31 * _hash + mSystemShortcut;
-        return _hash;
-    }
-
-    @DataClass.Generated(
-            time = 1722890917041L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java",
-            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final  int mModifierState\nprivate final @android.hardware.input.KeyboardSystemShortcut.SystemShortcut int mSystemShortcut\npublic static final  int SYSTEM_SHORTCUT_UNSPECIFIED\npublic static final  int SYSTEM_SHORTCUT_HOME\npublic static final  int SYSTEM_SHORTCUT_RECENT_APPS\npublic static final  int SYSTEM_SHORTCUT_BACK\npublic static final  int SYSTEM_SHORTCUT_APP_SWITCH\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_TASKBAR\npublic static final  int SYSTEM_SHORTCUT_TAKE_SCREENSHOT\npublic static final  int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_UP\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final  int SYSTEM_SHORTCUT_VOLUME_UP\npublic static final  int SYSTEM_SHORTCUT_VOLUME_DOWN\npublic static final  int SYSTEM_SHORTCUT_VOLUME_MUTE\npublic static final  int SYSTEM_SHORTCUT_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SEARCH\npublic static final  int SYSTEM_SHORTCUT_LANGUAGE_SWITCH\npublic static final  int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_MUTE\npublic static final  int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS\npublic static final  int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT\npublic static final  int SYSTEM_SHORTCUT_LOCK_SCREEN\npublic static final  int SYSTEM_SHORTCUT_OPEN_NOTES\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_POWER\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_SLEEP\npublic static final  int SYSTEM_SHORTCUT_WAKEUP\npublic static final  int SYSTEM_SHORTCUT_MEDIA_KEY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final  int SYSTEM_SHORTCUT_DESKTOP_MODE\npublic static final  int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION\nclass KeyboardSystemShortcut extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 1fb7937..29ccb85 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -145,10 +145,11 @@
      * @param screenBrightnessInt The overridden screen brightness between 1 and 255, or
      * {@link PowerManager#BRIGHTNESS_DEFAULT} to disable the override. Not used if
      *                            screenBrightnessFloat is provided (is not NaN).
+     * @param useNormalBrightnessForDoze Whether use normal brightness while device is dozing.
      */
     public abstract void setDozeOverrideFromDreamManager(
             int screenState, @Display.StateReason int reason, float screenBrightnessFloat,
-            int screenBrightnessInt);
+            int screenBrightnessInt, boolean useNormalBrightnessForDoze);
 
     /**
      * Used by sidekick manager to tell the power manager if it shouldn't change the display state
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 3fe063d..4c4aa6c 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -223,3 +223,14 @@
     description: "Show access entry of location bypass permission in the Privacy Dashboard"
     bug: "325536053"
 }
+
+flag {
+    name: "dont_remove_existing_uid_states"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Double check if the uid still exists before attempting to remove its appops state"
+    bug: "353474742"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 133b3d1..9c281f3 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -17,8 +17,8 @@
 package android.service.dreams;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
 import static android.service.dreams.Flags.dreamHandlesBeingObscured;
+import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
 import static android.service.dreams.Flags.startAndStopDozingInBackground;
 
 import android.annotation.FlaggedApi;
@@ -272,6 +272,9 @@
     private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
     private float mDozeScreenBrightnessFloat = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
+    // This variable being true means dozing device expecting normal(non-doze) brightness.
+    private boolean mUseNormalBrightnessForDoze;
+
     private boolean mDebug = false;
 
     private ComponentName mDreamComponent;
@@ -935,13 +938,14 @@
                 if (startAndStopDozingInBackground()) {
                     mDreamManager.startDozingOneway(
                             mDreamToken, mDozeScreenState, mDozeScreenStateReason,
-                            mDozeScreenBrightnessFloat, mDozeScreenBrightness);
+                            mDozeScreenBrightnessFloat, mDozeScreenBrightness,
+                            mUseNormalBrightnessForDoze);
                 } else {
                     mDreamManager.startDozing(
                             mDreamToken, mDozeScreenState, mDozeScreenStateReason,
-                            mDozeScreenBrightnessFloat, mDozeScreenBrightness);
+                            mDozeScreenBrightnessFloat, mDozeScreenBrightness,
+                            mUseNormalBrightnessForDoze);
                 }
-
             } catch (RemoteException ex) {
                 // system server died
             }
@@ -1010,7 +1014,8 @@
      */
     @UnsupportedAppUsage
     public void setDozeScreenState(int state) {
-        setDozeScreenState(state, Display.STATE_REASON_UNKNOWN);
+        setDozeScreenState(state, Display.STATE_REASON_UNKNOWN,
+                /* useNormalBrightnessForDoze= */ false);
     }
 
     /**
@@ -1048,21 +1053,43 @@
      * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
      * for the default behavior.
      * @param reason the reason for setting the specified screen state.
-     *
-     * @hide For use by system UI components only.
+     * @param useNormalBrightnessForDoze False means the default case where doze brightness is
+     * expected when device is dozing. True means display expects normal brightness for next doze
+     * request. Noted: unlike {@link #setDozeScreenBrightness} that sets a real brightness value for
+     * doze screen, this parameter only indicates whether the doze brightness is intended on next
+     * doze screen. The actual brightness value will be computed by {@link DisplayManager}
+     * internally.
+     * @hide For use by System UI components only.
      */
     @UnsupportedAppUsage
-    public void setDozeScreenState(int state, @Display.StateReason int reason) {
+    public void setDozeScreenState(int state, @Display.StateReason int reason,
+            boolean useNormalBrightnessForDoze) {
         synchronized (this) {
-            if (mDozeScreenState != state) {
+            if (mDozeScreenState != state
+                    || mUseNormalBrightnessForDoze != useNormalBrightnessForDoze) {
                 mDozeScreenState = state;
                 mDozeScreenStateReason = reason;
+                mUseNormalBrightnessForDoze = useNormalBrightnessForDoze;
                 updateDoze();
             }
         }
     }
 
     /**
+     * Returns whether we want to use the normal brightness setting while in doze. This is useful
+     * on devices like Wear; when we allow the user to interact with a device that remains in
+     * doze (looking at time).
+     *
+     * @return a boolean that informs {@link DisplayManager} whether to adjust the display for the
+     * interacting user e.g. brightening the display.
+     * @hide For use by System UI components only.
+     */
+    @UnsupportedAppUsage
+    public boolean getUseNormalBrightnessForDoze() {
+        return mUseNormalBrightnessForDoze;
+    }
+
+    /**
      * Gets the screen brightness to use while dozing.
      *
      * @return The screen brightness while dozing as a value between
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 611e791..1c0a2c0 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -43,7 +43,7 @@
     void finishSelf(in IBinder token, boolean immediate);
     /** @deprecated Please use startDozingOneway instead. */
     void startDozing(in IBinder token, int screenState, int reason, float screenBrightnessFloat,
-            int screenBrightnessInt);
+            int screenBrightnessInt, boolean useNormalBrightnessForDoze);
     void stopDozing(in IBinder token);
     void forceAmbientDisplayEnabled(boolean enabled);
     ComponentName[] getDreamComponentsForUser(int userId);
@@ -54,6 +54,7 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)")
     oneway void setDreamIsObscured(in boolean isObscured);
     oneway void startDozingOneway(in IBinder token, int screenState, int reason,
-            float screenBrightnessFloat, int screenBrightnessInt);
+            float screenBrightnessFloat, int screenBrightnessInt,
+            boolean useNormalBrightnessForDoze);
     oneway void finishSelfOneway(in IBinder token, boolean immediate);
 }
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3d190fe..5c5a2f6 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -267,3 +267,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "typeface_cache_for_var_settings"
+  namespace: "text"
+  description: "Cache Typeface instance for font variation settings."
+  bug: "355462362"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
\ No newline at end of file
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 3adbd68..9f54d9f 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -24,7 +24,7 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.Network;
-import android.net.NetworkInfo;
+import android.net.NetworkCapabilities;
 import android.net.SntpClient;
 import android.os.Build;
 import android.os.SystemClock;
@@ -687,8 +687,16 @@
             if (connectivityManager == null) {
                 return false;
             }
-            final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
-
+            final NetworkCapabilities networkCapabilities =
+                    connectivityManager.getNetworkCapabilities(network);
+            if (networkCapabilities == null) {
+                if (LOGD) Log.d(TAG, "getNetwork: failed to get network capabilities");
+                return false;
+            }
+            final boolean isConnectedToInternet = networkCapabilities.hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    && networkCapabilities.hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_VALIDATED);
             // This connectivity check is to avoid performing a DNS lookup for the time server on a
             // unconnected network. There are races to obtain time in Android when connectivity
             // changes, which means that forceRefresh() can be called by various components before
@@ -698,8 +706,8 @@
             // A side effect of check is that tests that run a fake NTP server on the device itself
             // will only be able to use it if the active network is connected, even though loopback
             // addresses are actually reachable.
-            if (ni == null || !ni.isConnected()) {
-                if (LOGD) Log.d(TAG, "getNetwork: no connectivity");
+            if (!isConnectedToInternet) {
+                if (LOGD) Log.d(TAG, "getNetwork: no internet connectivity");
                 return false;
             }
             return true;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1c0700f..e97f603 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -133,6 +133,7 @@
 import static com.android.window.flags.Flags.insetsControlChangedItem;
 import static com.android.window.flags.Flags.insetsControlSeq;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
+import static com.android.window.flags.Flags.systemUiImmersiveConfirmationDialog;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -378,7 +379,7 @@
      * @hide
      */
     public static final boolean CLIENT_IMMERSIVE_CONFIRMATION =
-            SystemProperties.getBoolean("persist.wm.debug.client_immersive_confirmation", false);
+            systemUiImmersiveConfirmationDialog();
 
     /**
      * Set this system property to true to force the view hierarchy to render
diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS
index b3583a7..48052c6 100644
--- a/core/java/android/view/contentprotection/OWNERS
+++ b/core/java/android/view/contentprotection/OWNERS
@@ -1,4 +1,6 @@
-# Bug component: 544200
+# Bug component: 1040349
 
-include /core/java/android/view/contentcapture/OWNERS
+njagar@google.com
+williamluh@google.com
+aaronjosephs@google.com
 
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d12eda3..fe26510 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -741,9 +741,17 @@
     public abstract boolean getUseWideViewPort();
 
     /**
-     * Sets whether the WebView whether supports multiple windows. If set to
-     * true, {@link WebChromeClient#onCreateWindow} must be implemented by the
-     * host application. The default is {@code false}.
+     * Sets whether the WebView should support multiple windows.
+     *
+     * <p>If set to {@code true}, the {@link WebChromeClient#onCreateWindow}
+     * callback must be implemented by the application to handle the
+     * creation of new windows.
+     *
+     * <p>The default is {@code false}. When multiple window support is disabled,
+     * requests to open new windows (either from the {@code window.open()}
+     * JavaScript API or from links with {@code target="_blank"}) will instead
+     * be treated as top-level navigations, replacing the current page in the
+     * same WebView.
      *
      * @param support whether to support multiple windows
      */
@@ -1338,18 +1346,24 @@
     }
 
     /**
-     * Tells JavaScript to open windows automatically. This applies to the
-     * JavaScript function {@code window.open()}. The default is {@code false}.
+     * Allows JavaScript to open windows without a user gesture. This applies to
+     * the JavaScript function {@code window.open()}. The default is
+     * {@code false}: attempts without a user gesture will fail and do nothing.
+     * <p>
+     * This is not affected by the {@link #setSupportMultipleWindows} setting;
+     * the user gesture requirement is enforced even if multiple windows are
+     * disabled.
      *
-     * @param flag {@code true} if JavaScript can open windows automatically
+     * @param flag {@code true} if JavaScript can open windows without a user
+     *             gesture.
      */
     public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean flag);
 
     /**
-     * Gets whether JavaScript can open windows automatically.
+     * Gets whether JavaScript can open windows without a user gesture.
      *
-     * @return {@code true} if JavaScript can open windows automatically during
-     *         {@code window.open()}
+     * @return {@code true} if JavaScript can open windows without a user
+     *         gesture using {@code window.open()}
      * @see #setJavaScriptCanOpenWindowsAutomatically
      */
     public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 9aeccf4..61ee13a 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -91,6 +91,14 @@
 }
 
 flag {
+  name: "transit_tracker_plumbing"
+  namespace: "windowing_frontend"
+  description: "Plumb and collect on transition tracking object instead of singleton"
+  bug: "325114242"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "transit_ready_tracking"
   namespace: "windowing_frontend"
   description: "Enable accurate transition readiness tracking"
@@ -202,6 +210,14 @@
 }
 
 flag {
+  name: "system_ui_immersive_confirmation_dialog"
+  namespace: "windowing_frontend"
+  description: "Enable the implementation of the immersive confirmation dialog on system UI side by default"
+  bug: "359713629"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "ensure_wallpaper_in_transitions"
   namespace: "windowing_frontend"
   description: "Ensure that wallpaper window tokens are always present/available for collection in transitions"
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 27eebbe..ce17d78 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -74,6 +74,7 @@
          * @param locale    the locale picked.
          */
         void onLocaleSelected(LocaleStore.LocaleInfo locale);
+        default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {}
     }
 
     /**
@@ -292,7 +293,7 @@
                         mListener, locale, mTranslatedOnly /* translate only */,
                         mOnActionExpandListener, this.mLocalePickerCollector);
             }
-
+            mListener.onParentLocaleSelected(locale);
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 572a599..fcc3023 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -48,6 +48,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -423,6 +424,14 @@
         for (IProtoLogGroup group : protoLogGroups) {
             mLogGroups.put(group.name(), group);
         }
+
+        final var hasGroupsLoggingToLogcat = Arrays.stream(protoLogGroups)
+                .anyMatch(IProtoLogGroup::isLogToLogcat);
+
+        final ILogger logger = (msg) -> Slog.i(TAG, msg);
+        if (hasGroupsLoggingToLogcat) {
+            mViewerConfig.loadViewerConfig(logger, mLegacyViewerConfigFilename);
+        }
     }
 }
 
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index cb20ceb..14786e1 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.protolog;
 
+import static android.content.Context.PROTOLOG_SERVICE;
 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE;
 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS;
 import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID;
@@ -46,6 +47,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -76,6 +79,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.UUID;
@@ -103,36 +107,45 @@
     private final ProtoLogViewerConfigReader mViewerConfigReader;
     @Nullable
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+    @NonNull
     private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+    @NonNull
     private final Runnable mCacheUpdater;
 
+    @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
+    private final IProtoLogService mProtoLogService;
+
+    @NonNull
     private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
+    @NonNull
     private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>();
+    @NonNull
     private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
 
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
-    public PerfettoProtoLogImpl(@NonNull String viewerConfigFilePath, Runnable cacheUpdater) {
-        this(() -> {
-            try {
-                return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
-            } catch (FileNotFoundException e) {
-                throw new RuntimeException("Failed to load viewer config file " + viewerConfigFilePath, e);
-            }
-        }, cacheUpdater);
+    public PerfettoProtoLogImpl() {
+        this(null, null, null, () -> {});
     }
 
-    public PerfettoProtoLogImpl() {
-        this(null, null, () -> {});
+    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater) {
+        this(null, null, null, cacheUpdater);
     }
 
     public PerfettoProtoLogImpl(
-            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            Runnable cacheUpdater
-    ) {
-        this(viewerConfigInputStreamProvider,
-                new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider),
+            @NonNull String viewerConfigFilePath,
+            @NonNull Runnable cacheUpdater) {
+        this(viewerConfigFilePath,
+                null,
+                new ProtoLogViewerConfigReader(() -> {
+                    try {
+                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+                    } catch (FileNotFoundException e) {
+                        throw new RuntimeException(
+                                "Failed to load viewer config file " + viewerConfigFilePath, e);
+                    }
+                }),
                 cacheUpdater);
     }
 
@@ -140,8 +153,18 @@
     public PerfettoProtoLogImpl(
             @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
             @Nullable ProtoLogViewerConfigReader viewerConfigReader,
-            Runnable cacheUpdater
-    ) {
+            @NonNull Runnable cacheUpdater) {
+        this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater);
+    }
+
+    private PerfettoProtoLogImpl(
+            @Nullable String viewerConfigFilePath,
+            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
+            @NonNull Runnable cacheUpdater) {
+        assert (viewerConfigFilePath == null || viewerConfigInputStreamProvider == null) :
+                "Only one of viewerConfigFilePath and viewerConfigInputStreamProvider can be set";
+
         Producer.init(InitArguments.DEFAULTS);
         DataSourceParams params =
                 new DataSourceParams.Builder()
@@ -153,6 +176,27 @@
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
         this.mViewerConfigReader = viewerConfigReader;
         this.mCacheUpdater = cacheUpdater;
+
+        if (android.tracing.Flags.clientSideProtoLogging()) {
+            mProtoLogService =
+                    IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
+            Objects.requireNonNull(mProtoLogService,
+                    "ServiceManager returned a null ProtoLog service");
+
+            try {
+                var args = new ProtoLogService.RegisterClientArgs();
+
+                if (viewerConfigFilePath != null) {
+                    args.setViewerConfigFile(viewerConfigFilePath);
+                }
+
+                mProtoLogService.registerClient(this, args);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Failed to register ProtoLog client");
+            }
+        } else {
+            mProtoLogService = null;
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 77ca7ce..8659a8f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -111,7 +111,7 @@
                     // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
                     // In so tests the viewer config file might not exist in which we don't
                     // want to provide config path to the user
-                    sServiceInstance = new PerfettoProtoLogImpl(null, null, sCacheUpdater);
+                    sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater);
                 } else {
                     sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
                 }
diff --git a/core/jni/android_database_SQLiteRawStatement.cpp b/core/jni/android_database_SQLiteRawStatement.cpp
index 9614864..85a6bdf 100644
--- a/core/jni/android_database_SQLiteRawStatement.cpp
+++ b/core/jni/android_database_SQLiteRawStatement.cpp
@@ -72,14 +72,17 @@
 
 
 // This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out
-// of bounds.
-static void throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
+// of bounds.  It returns true if an exception was thrown.
+static bool throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
     if (col < 0 || col >= sqlite3_data_count(stmt(stmtPtr))) {
         int count = sqlite3_data_count(stmt(stmtPtr));
         std::string message = android::base::StringPrintf(
             "column index %d out of bounds [0,%d]", col, count - 1);
         char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
         throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str());
+        return true;
+    } else {
+        return false;
     }
 }
 
@@ -216,12 +219,16 @@
 
 
 static jint columnType(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_type(stmt(stmtPtr), col);
 }
 
 static jstring columnName(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return nullptr;
+    }
     const jchar* name = static_cast<const jchar*>(sqlite3_column_name16(stmt(stmtPtr), col));
     if (name == nullptr) {
         throw_sqlite3_exception(env, db(stmtPtr), "error fetching columnName()");
@@ -232,14 +239,18 @@
 }
 
 static jint columnBytes(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     int r = sqlite3_column_bytes16(stmt(stmtPtr), col);
     throwIfError(env, stmtPtr);
     return r;
 }
 
 static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return nullptr;
+    }
     const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
     if (blob == nullptr) {
         if (throwIfError(env, stmtPtr)) {
@@ -262,7 +273,9 @@
 
 static int columnBuffer(JNIEnv* env, jclass, jlong stmtPtr, jint col,
         jbyteArray buffer, jint offset, jint length, jint srcOffset) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
     if (blob == nullptr) {
         throwIfError(env, stmtPtr);
@@ -281,22 +294,30 @@
 }
 
 static jdouble columnDouble(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_double(stmt(stmtPtr), col);
 }
 
 static jint columnInt(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_int(stmt(stmtPtr), col);
 }
 
 static jlong columnLong(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_int64(stmt(stmtPtr), col);
 }
 
 static jstring columnText(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return nullptr;
+    }
     const jchar* text = static_cast<const jchar*>(sqlite3_column_text16(stmt(stmtPtr), col));
     if (text == nullptr) {
         throwIfError(env, stmtPtr);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7aeabee..117041a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8132,10 +8132,10 @@
     <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
                 android:protectionLevel="signature" />
 
-    <!-- Allows low-level access to monitor keyboard system shortcuts
+    <!-- Allows low-level access to manage key gestures.
          <p>Not for use by third-party applications.
          @hide -->
-    <permission android:name="android.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS"
+    <permission android:name="android.permission.MANAGE_KEY_GESTURES"
                 android:protectionLevel="signature" />
 
     <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
diff --git a/core/res/res/drawable-car/car_activity_resolver_list_background.xml b/core/res/res/drawable-car/car_activity_resolver_list_background.xml
deleted file mode 100644
index dbbadd8..0000000
--- a/core/res/res/drawable-car/car_activity_resolver_list_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="?attr/colorBackgroundFloating" />
-    <corners android:radius="@dimen/car_activity_resolver_corner_radius" />
-</shape>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list.xml b/core/res/res/layout-car/car_resolver_list.xml
deleted file mode 100644
index 08c9861..0000000
--- a/core/res/res/layout-car/car_resolver_list.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* 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.
-*/
--->
-<com.android.internal.widget.ResolverDrawerLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/car_activity_resolver_width"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:id="@id/contentPanel">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:background="@drawable/car_activity_resolver_list_background">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@drawable/car_activity_resolver_list_background"
-            android:orientation="horizontal"
-            android:paddingVertical="@dimen/car_padding_4"
-            android:paddingHorizontal="@dimen/car_padding_4" >
-            <TextView
-                android:id="@+id/profile_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="start"
-                android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
-        </LinearLayout>
-
-        <FrameLayout
-            android:id="@+id/stub"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-
-        <TabHost
-            android:id="@+id/profile_tabhost"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:background="?android:attr/colorBackgroundFloating">
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TabWidget
-                    android:id="@android:id/tabs"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-                </TabWidget>
-                <View
-                    android:id="@+id/resolver_tab_divider"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-                <FrameLayout
-                    android:id="@android:id/tabcontent"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-                    <com.android.internal.app.ResolverViewPager
-                        android:id="@+id/profile_pager"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"/>
-                </FrameLayout>
-            </LinearLayout>
-        </TabHost>
-
-        <LinearLayout
-            android:id="@+id/button_bar"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginVertical="@dimen/car_padding_4"
-            android:layout_marginHorizontal="@dimen/car_padding_4"
-            android:padding="0dp"
-            android:gravity="center"
-            android:background="@drawable/car_activity_resolver_list_background"
-            android:orientation="vertical">
-
-            <Button
-                android:id="@+id/button_once"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:layout_marginBottom="@dimen/car_padding_2"
-                android:text="@string/activity_resolver_use_once"
-                android:onClick="onButtonClick"/>
-
-            <Button
-                android:id="@+id/button_always"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:text="@string/activity_resolver_use_always"
-                android:onClick="onButtonClick"/>
-        </LinearLayout>
-
-    </LinearLayout>
-
-</com.android.internal.widget.ResolverDrawerLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list_with_default.xml b/core/res/res/layout-car/car_resolver_list_with_default.xml
deleted file mode 100644
index 08cc7ff..0000000
--- a/core/res/res/layout-car/car_resolver_list_with_default.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* 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.
-*/
--->
-<com.android.internal.widget.ResolverDrawerLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/car_activity_resolver_width"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:id="@id/contentPanel">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:layout_gravity="center"
-        android:background="@drawable/car_activity_resolver_list_background">
-
-        <FrameLayout
-            android:id="@+id/stub"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@drawable/car_activity_resolver_list_background"/>
-
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:minHeight="@dimen/car_activity_resolver_list_item_height"
-            android:orientation="horizontal">
-
-            <RadioButton
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:focusable="false"
-                android:clickable="false"
-                android:layout_marginStart="?attr/listPreferredItemPaddingStart"
-                android:layout_gravity="start|center_vertical"
-                android:checked="true"/>
-
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="@dimen/car_icon_size"
-                android:layout_height="@dimen/car_icon_size"
-                android:layout_gravity="start|center_vertical"
-                android:layout_marginStart="@dimen/car_padding_4"
-                android:src="@drawable/resolver_icon_placeholder"
-                android:scaleType="fitCenter"/>
-
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginHorizontal="?attr/listPreferredItemPaddingStart"
-                style="?android:attr/textAppearanceListItem"
-                android:layout_gravity="start|center_vertical" />
-
-            <LinearLayout
-                android:id="@+id/profile_button"
-                android:visibility="gone"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content">
-
-                <ImageView
-                    android:id="@+id/icon"
-                    android:visibility="gone"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-
-                <TextView
-                    android:id="@id/text1"
-                    android:visibility="gone"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-            </LinearLayout>
-        </LinearLayout>
-
-        <TabHost
-            android:id="@+id/profile_tabhost"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:background="?attr/colorBackgroundFloating">
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TabWidget
-                    android:id="@android:id/tabs"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:visibility="gone">
-                </TabWidget>
-                <View
-                    android:id="@+id/resolver_tab_divider"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-                <FrameLayout
-                    android:id="@android:id/tabcontent"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-                    <com.android.internal.app.ResolverViewPager
-                        android:id="@+id/profile_pager"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content">
-                    </com.android.internal.app.ResolverViewPager>
-                </FrameLayout>
-            </LinearLayout>
-        </TabHost>
-
-        <LinearLayout
-            android:id="@+id/button_bar"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginVertical="@dimen/car_padding_4"
-            android:layout_marginHorizontal="@dimen/car_padding_4"
-            android:gravity="center"
-            android:background="@drawable/car_activity_resolver_list_background"
-            android:orientation="vertical">
-
-            <Button
-                android:id="@+id/button_once"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:layout_marginBottom="@dimen/car_padding_2"
-                android:text="@string/activity_resolver_use_once"
-                android:onClick="onButtonClick"/>
-
-            <Button
-                android:id="@+id/button_always"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:text="@string/activity_resolver_use_always"
-                android:onClick="onButtonClick"/>
-        </LinearLayout>
-    </LinearLayout>
-
-</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 832ebe5..6dad3b7 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -999,4 +999,31 @@
             mDatabase.endTransaction();
         }
     }
+
+    /**
+     * This test verifies that the JNI exception thrown because of a bad column is actually thrown
+     * and does not crash the VM.
+     */
+    @Test
+    public void testJniExceptions() {
+        // Create the t1 table.
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mDatabase.beginTransactionReadOnly();
+        try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT * from t1")) {
+            s.step();
+            s.getColumnText(5); // out-of-range column
+            fail("JNI exception not thrown");
+        } catch (SQLiteBindOrColumnIndexOutOfRangeException e) {
+            // Passing case.
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
new file mode 100644
index 0000000..8a54e5b
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * PaintTest tests {@link Paint}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PaintFontVariationTest extends InstrumentationTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    @Test
+    public void testDerivedFromSameTypeface() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    @Test
+    public void testDerivedFromChained() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
+}
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 0dec756..878ba70 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -16,13 +16,22 @@
 
 package android.graphics;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertNotEquals;
 
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+
 import java.util.Arrays;
 import java.util.HashSet;
 
@@ -30,6 +39,9 @@
  * PaintTest tests {@link Paint}.
  */
 public class PaintTest extends InstrumentationTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
 
     static void assertEquals(String message, float[] expected, float[] actual) {
@@ -403,4 +415,33 @@
         assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
         assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
     }
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    public void testDerivedFromSameTypeface() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    public void testDerivedFromChained() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
 }
diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS
index b3583a7..3d09da3 100644
--- a/core/tests/coretests/src/android/view/contentprotection/OWNERS
+++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 544200
+# Bug component: 1040349
 
-include /core/java/android/view/contentcapture/OWNERS
+include /core/java/android/view/contentprotection/OWNERS
 
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index fd78816..889a7785 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -56,6 +56,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -74,6 +75,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -143,6 +145,23 @@
     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
     private static final Object sDynamicCacheLock = new Object();
 
+    private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache =
+            new LruCache<>(16);
+    private static final Object sVariableCacheLock = new Object();
+
+    /** @hide */
+    @VisibleForTesting
+    public static void clearTypefaceCachesForTestingPurpose() {
+        synchronized (sWeightCacheLock) {
+            sWeightTypefaceCache.clear();
+        }
+        synchronized (sDynamicCacheLock) {
+            sDynamicTypefaceCache.evictAll();
+        }
+        synchronized (sVariableCacheLock) {
+            sVariableCache.evictAll();
+        }
+    }
 
     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
     static Typeface sDefaultTypeface;
@@ -195,6 +214,8 @@
     @UnsupportedAppUsage
     public final long native_instance;
 
+    private final Typeface mDerivedFrom;
+
     private final String mSystemFontFamilyName;
 
     private final Runnable mCleaner;
@@ -274,6 +295,18 @@
     }
 
     /**
+     * Returns the Typeface used for creating this Typeface.
+     *
+     * Maybe null if this is not derived from other Typeface.
+     * TODO(b/357707916): Make this public API.
+     * @hide
+     */
+    @VisibleForTesting
+    public final @Nullable Typeface getDerivedFrom() {
+        return mDerivedFrom;
+    }
+
+    /**
      * Returns the system font family name if the typeface was created from a system font family,
      * otherwise returns null.
      */
@@ -1021,9 +1054,51 @@
         return typeface;
     }
 
-    /** @hide */
+    private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) {
+        // The given list can be mutated because it is allocated in Paint#setFontVariationSettings.
+        // Currently, Paint#setFontVariationSettings is the only code path reaches this method.
+        axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue));
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < axes.size(); ++i) {
+            final FontVariationAxis fva = axes.get(i);
+            sb.append(fva.getTag());
+            sb.append(fva.getStyleValue());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * TODO(b/357707916): Make this public API.
+     * @hide
+     */
     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
             @NonNull List<FontVariationAxis> axes) {
+        if (Flags.typefaceCacheForVarSettings()) {
+            final Typeface target = (family == null) ? Typeface.DEFAULT : family;
+            final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom;
+
+            final String key = axesToVarKey(axes);
+
+            synchronized (sVariableCacheLock) {
+                LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
+                if (innerCache == null) {
+                    // Cache up to 16 var instance per root Typeface
+                    innerCache = new LruCache<>(16);
+                    sVariableCache.put(base.native_instance, innerCache);
+                } else {
+                    Typeface cached = innerCache.get(key);
+                    if (cached != null) {
+                        return cached;
+                    }
+                }
+                Typeface typeface = new Typeface(
+                        nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
+                        base.getSystemFontFamilyName(), base);
+                innerCache.put(key, typeface);
+                return typeface;
+            }
+        }
+
         final Typeface base = family == null ? Typeface.DEFAULT : family;
         Typeface typeface = new Typeface(
                 nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
@@ -1184,11 +1259,19 @@
     // don't allow clients to call this directly
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private Typeface(long ni) {
-        this(ni, null);
+        this(ni, null, null);
+    }
+
+
+    // don't allow clients to call this directly
+    // This is kept for robolectric.
+    private Typeface(long ni, @Nullable String systemFontFamilyName) {
+        this(ni, systemFontFamilyName, null);
     }
 
     // don't allow clients to call this directly
-    private Typeface(long ni, @Nullable String systemFontFamilyName) {
+    private Typeface(long ni, @Nullable String systemFontFamilyName,
+            @Nullable Typeface derivedFrom) {
         if (ni == 0) {
             throw new RuntimeException("native typeface cannot be made");
         }
@@ -1198,6 +1281,7 @@
         mStyle = nativeGetStyle(ni);
         mWeight = nativeGetWeight(ni);
         mSystemFontFamilyName = systemFontFamilyName;
+        mDerivedFrom = derivedFrom;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 56ea7c2..9de10c0 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -128,3 +128,13 @@
     description: "Enable bubble bar to be shown in the persistent task bar"
     bug: "346391377"
 }
+
+flag {
+    name: "bubble_view_info_executors"
+    namespace: "multitasking"
+    description: "Use executors to inflate bubble views"
+    bug: "353894869"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8627c0b..53ab2d5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -539,20 +539,23 @@
     <!-- The size of the icon shown in the resize veil. -->
     <dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen>
 
-    <!-- The with of the border around the app task for edge resizing, when
+    <!-- The width of the border outside the app task eligible for edge resizing, when
          enable_windowing_edge_drag_resize is enabled. -->
-    <dimen name="desktop_mode_edge_handle">12dp</dimen>
+    <dimen name="freeform_edge_handle_outset">10dp</dimen>
+
+    <!-- The size of the border inside the app task eligible for edge resizing, when
+         enable_windowing_edge_drag_resize is enabled. -->
+    <dimen name="freeform_edge_handle_inset">2dp</dimen>
 
     <!-- The original width of the border around the app task for edge resizing, when
          enable_windowing_edge_drag_resize is disabled. -->
     <dimen name="freeform_resize_handle">15dp</dimen>
 
     <!-- The size of the corner region for drag resizing with touch, when a larger touch region is
-         appropriate. Applied when enable_windowing_edge_drag_resize is enabled. -->
+         appropriate. -->
     <dimen name="desktop_mode_corner_resize_large">48dp</dimen>
 
-    <!-- The original size of the corner region for darg resizing, when
-         enable_windowing_edge_drag_resize is disabled. -->
+    <!-- The size of the corner region for drag resizing with a cursor or a stylus. -->
     <dimen name="freeform_resize_corner">44dp</dimen>
 
     <!-- The thickness in dp for all desktop drag transition regions. -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 282385a..341ca0e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -24,6 +24,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
 
+import java.io.PrintWriter;
+
 /**
  * Constants for desktop mode feature
  */
@@ -203,4 +205,19 @@
     private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
         return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
     }
+
+    /** Dumps DesktopModeStatus flags and configs. */
+    public static void dump(PrintWriter pw, String prefix, Context context) {
+        String innerPrefix = prefix + "  ";
+        pw.print(prefix); pw.println(TAG);
+        pw.print(innerPrefix); pw.print("maxTaskLimit="); pw.println(getMaxTaskLimit(context));
+
+        pw.print(innerPrefix); pw.print("maxTaskLimit config override=");
+        pw.println(context.getResources().getInteger(
+                R.integer.config_maxDesktopWindowingActiveTasks));
+
+        SystemProperties.Handle maxTaskLimitHandle = SystemProperties.find(MAX_TASK_LIMIT_SYS_PROP);
+        pw.print(innerPrefix); pw.print("maxTaskLimit sysprop=");
+        pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1));
+    }
 }
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 1242287..a0a9451 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
@@ -146,7 +146,8 @@
     private final Handler mBgHandler;
     private final WindowManager mWindowManager;
     private final Transitions mTransitions;
-    private final BackTransitionHandler mBackTransitionHandler;
+    @VisibleForTesting
+    final BackTransitionHandler mBackTransitionHandler;
     @VisibleForTesting
     final Rect mTouchableArea = new Rect();
 
@@ -174,7 +175,8 @@
     @Nullable
     private IOnBackInvokedCallback mActiveCallback;
     @Nullable
-    private RemoteAnimationTarget[] mApps;
+    @VisibleForTesting
+    RemoteAnimationTarget[] mApps;
 
     @VisibleForTesting
     final RemoteCallback mNavigationObserver = new RemoteCallback(
@@ -1448,7 +1450,8 @@
          * Check whether this transition is prepare for predictive back animation, which could
          * happen when core make an activity become visible.
          */
-        private boolean handlePrepareTransition(
+        @VisibleForTesting
+        boolean handlePrepareTransition(
                 @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
@@ -1491,7 +1494,8 @@
          * Check whether this transition is triggered from back gesture commitment.
          * Reparent the transition targets to animation leashes, so the animation won't be broken.
          */
-        private boolean handleCloseTransition(@NonNull TransitionInfo info,
+        @VisibleForTesting
+        boolean handleCloseTransition(@NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f54b44b..544c2dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1045,6 +1045,17 @@
             wct.reorder(task.token, true)
             return wct
         }
+        // If task is already visible, it must have been handled already and added to desktop mode.
+        // Cascade task only if it's not visible yet.
+        if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)
+                && !taskRepository.isVisibleTask(task.taskId)) {
+            val displayLayout = displayController.getDisplayLayout(task.displayId)
+            if (displayLayout != null) {
+                val initialBounds = Rect(task.configuration.windowConfiguration.bounds)
+                cascadeWindow(task, initialBounds, displayLayout)
+                wct.setBounds(task.token, initialBounds)
+            }
+        }
         if (useDesktopOverrideDensity()) {
             wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE)
         }
@@ -1097,16 +1108,21 @@
     /** Handle task closing by removing wallpaper activity if it's the last active task */
     private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
         logV("handleTaskClosing")
+        if (!isDesktopModeShowing(task.displayId))
+            return null
+
         val wct = WindowContainerTransaction()
         if (taskRepository.isOnlyVisibleNonClosingTask(task.taskId)
-            && taskRepository.wallpaperActivityToken != null) {
+            && taskRepository.wallpaperActivityToken != null
+        ) {
             // Remove wallpaper activity when the last active task is removed
             removeWallpaperActivity(wct)
         }
         taskRepository.addClosingTask(task.displayId, task.taskId)
         // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
         if (DesktopModeFlags.BACK_NAVIGATION.isEnabled(context) &&
-            taskRepository.isVisibleTask(task.taskId)) {
+            taskRepository.isVisibleTask(task.taskId)
+        ) {
             wct.removeTask(task.token)
         }
         return if (wct.isEmpty) null else wct
@@ -1134,18 +1150,9 @@
         }
 
         if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)) {
-            val stableBounds = Rect()
-            displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
-            val activeTasks = taskRepository
-                .getActiveNonMinimizedOrderedTasks(taskInfo.displayId)
-            activeTasks.firstOrNull()?.let { activeTask ->
-                shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let {
-                    cascadeWindow(context.resources, stableBounds,
-                        it.configuration.windowConfiguration.bounds, initialBounds)
-                }
-            }
+            cascadeWindow(taskInfo, initialBounds, displayLayout)
         }
+
         if (canChangeTaskPosition(taskInfo)) {
             wct.setBounds(taskInfo.token, initialBounds)
         }
@@ -1180,6 +1187,19 @@
         }
     }
 
+    private fun cascadeWindow(task: TaskInfo, bounds: Rect, displayLayout: DisplayLayout) {
+        val stableBounds = Rect()
+        displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+        val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(task.displayId)
+        activeTasks.firstOrNull()?.let { activeTask ->
+            shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let {
+                cascadeWindow(context.resources, stableBounds,
+                    it.configuration.windowConfiguration.bounds, bounds)
+            }
+        }
+    }
+
     /**
      * Adds split screen changes to a transaction. Note that bounds are not reset here due to
      * animation; see {@link onDesktopSplitSelectAnimComplete}
@@ -1513,6 +1533,7 @@
     private fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
         pw.println("${prefix}DesktopTasksController")
+        DesktopModeStatus.dump(pw, innerPrefix, context)
         taskRepository.dump(pw, innerPrefix)
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 37e2fd0..7ba6ec4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -290,6 +290,20 @@
 
         // Entering PIP.
         if (isEnteringPip(info)) {
+            if (!mPipTransitionState.isInPip() && TransitionUtil.hasDisplayChange(info)) {
+                final TransitionInfo.Change pipChange = getPipChange(info);
+                if (pipChange != null) {
+                    // Clear old crop.
+                    updatePipForUnhandledTransition(pipChange, startTransaction, finishTransaction);
+                }
+                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: ignore exited PiP with display change", TAG);
+                // This should be an exited pip. E.g. a display change transition happens when
+                // the exiting pip is animating, then mergeAnimation -> end -> onFinishResize ->
+                // onExitPipFinished was called, i.e. pip state is UNDEFINED. So do not handle
+                // the incoming transition as entering pip.
+                return false;
+            }
             if (handleEnteringPipWithDisplayChange(transition, info, startTransaction,
                     finishTransaction, finishCallback)) {
                 // The destination position is applied directly and let default transition handler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 401b78d..39260f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -19,6 +19,7 @@
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -286,7 +287,8 @@
         final Resources res = mResult.mRootView.getResources();
         mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
                 new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(mContext, res),
-                getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
+                getResizeHandleEdgeInset(res), getFineResizeCornerSize(res),
+                getLargeResizeCornerSize(res)), touchSlop);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 538d0fb..b5f5bb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -31,6 +31,7 @@
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -515,8 +516,8 @@
         if (mDragResizeListener.setGeometry(
                 new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
                         new Size(mResult.mWidth, mResult.mHeight),
-                        getResizeEdgeHandleSize(mContext, res), getFineResizeCornerSize(res),
-                        getLargeResizeCornerSize(res)), touchSlop)
+                        getResizeEdgeHandleSize(mContext, res), getResizeHandleEdgeInset(res),
+                        getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
             updateExclusionRegion();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index 014d61d..fd7bed7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -44,27 +44,32 @@
 final class DragResizeWindowGeometry {
     private final int mTaskCornerRadius;
     private final Size mTaskSize;
-    // The size of the handle applied to the edges of the window, for the user to drag resize.
-    private final int mResizeHandleThickness;
+    // The size of the handle outside the task window applied to the edges of the window, for the
+    // user to drag resize.
+    private final int mResizeHandleEdgeOutset;
+    // The size of the handle inside the task window applied to the edges of the window, for the
+    // user to drag resize.
+    private final int mResizeHandleEdgeInset;
     // The task corners to permit drag resizing with a course input, such as touch.
-
     private final @NonNull TaskCorners mLargeTaskCorners;
     // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
     private final @NonNull TaskCorners mFineTaskCorners;
     // The bounds for each edge drag region, which can resize the task in one direction.
-    private final @NonNull TaskEdges mTaskEdges;
+    final @NonNull TaskEdges mTaskEdges;
 
     DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
-            int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
+            int resizeHandleEdgeOutset, int resizeHandleEdgeInset, int fineCornerSize,
+            int largeCornerSize) {
         mTaskCornerRadius = taskCornerRadius;
         mTaskSize = taskSize;
-        mResizeHandleThickness = resizeHandleThickness;
+        mResizeHandleEdgeOutset = resizeHandleEdgeOutset;
+        mResizeHandleEdgeInset = resizeHandleEdgeInset;
 
         mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
         mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
 
         // Save touch areas for each edge.
-        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
+        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mResizeHandleEdgeInset);
     }
 
     /**
@@ -72,11 +77,18 @@
      */
     static int getResizeEdgeHandleSize(@NonNull Context context, @NonNull Resources res) {
         return EDGE_DRAG_RESIZE.isEnabled(context)
-                ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+                ? res.getDimensionPixelSize(R.dimen.freeform_edge_handle_outset)
                 : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
     }
 
     /**
+     * Returns the resource value to use for the edge resize handle inside the task bounds.
+     */
+    static int getResizeHandleEdgeInset(@NonNull Resources res) {
+        return res.getDimensionPixelSize(R.dimen.freeform_edge_handle_inset);
+    }
+
+    /**
      * Returns the resource value to use for course input, such as touch, that benefits from a large
      * square on each of the window's corners.
      */
@@ -95,7 +107,8 @@
     /**
      * Returns the size of the task this geometry is calculated for.
      */
-    @NonNull Size getTaskSize() {
+    @NonNull
+    Size getTaskSize() {
         // Safe to return directly since size is immutable.
         return mTaskSize;
     }
@@ -217,13 +230,15 @@
             ctrlType |= CTRL_TYPE_BOTTOM;
         }
         // If the touch is within one of the four corners, check if it is within the bounds of the
-        // // handle.
+        // handle.
         if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
                 && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
             return checkDistanceFromCenter(ctrlType, x, y);
         }
-        // Otherwise, we should make sure we don't resize tasks inside task bounds.
-        return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight())
+        // Allow a small resize handle inside the task bounds defined by the edge inset.
+        return (x <= mResizeHandleEdgeInset || y <= mResizeHandleEdgeInset
+                || x >= mTaskSize.getWidth() - mResizeHandleEdgeInset
+                || y >= mTaskSize.getHeight() - mResizeHandleEdgeInset)
                 ? ctrlType : CTRL_TYPE_UNDEFINED;
     }
 
@@ -237,7 +252,7 @@
         final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType);
         double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y);
 
-        if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+        if (distanceFromCenter < mTaskCornerRadius + mResizeHandleEdgeOutset
                 && distanceFromCenter >= mTaskCornerRadius) {
             return ctrlType;
         }
@@ -288,7 +303,8 @@
 
         return this.mTaskCornerRadius == other.mTaskCornerRadius
                 && this.mTaskSize.equals(other.mTaskSize)
-                && this.mResizeHandleThickness == other.mResizeHandleThickness
+                && this.mResizeHandleEdgeOutset == other.mResizeHandleEdgeOutset
+                && this.mResizeHandleEdgeInset == other.mResizeHandleEdgeInset
                 && this.mFineTaskCorners.equals(other.mFineTaskCorners)
                 && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
                 && this.mTaskEdges.equals(other.mTaskEdges);
@@ -299,7 +315,8 @@
         return Objects.hash(
                 mTaskCornerRadius,
                 mTaskSize,
-                mResizeHandleThickness,
+                mResizeHandleEdgeOutset,
+                mResizeHandleEdgeInset,
                 mFineTaskCorners,
                 mLargeTaskCorners,
                 mTaskEdges);
@@ -421,26 +438,27 @@
         private final @NonNull Rect mBottomEdgeBounds;
         private final @NonNull Region mRegion;
 
-        private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) {
+        private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness,
+                int resizeHandleEdgeInset) {
             // Save touch areas for each edge.
             mTopEdgeBounds = new Rect(
                     -resizeHandleThickness,
                     -resizeHandleThickness,
                     taskSize.getWidth() + resizeHandleThickness,
-                    0);
+                    resizeHandleThickness);
             mLeftEdgeBounds = new Rect(
                     -resizeHandleThickness,
                     0,
-                    0,
+                    resizeHandleEdgeInset,
                     taskSize.getHeight());
             mRightEdgeBounds = new Rect(
-                    taskSize.getWidth(),
+                    taskSize.getWidth() - resizeHandleEdgeInset,
                     0,
                     taskSize.getWidth() + resizeHandleThickness,
                     taskSize.getHeight());
             mBottomEdgeBounds = new Rect(
                     -resizeHandleThickness,
-                    taskSize.getHeight(),
+                    taskSize.getHeight() - resizeHandleEdgeInset,
                     taskSize.getWidth() + resizeHandleThickness,
                     taskSize.getHeight() + resizeHandleThickness);
 
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 56fad95..311b1c5 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
@@ -16,8 +16,19 @@
 
 package com.android.wm.shell.back;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.window.BackNavigationInfo.KEY_NAVIGATION_FINISHED;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +36,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -32,6 +44,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.app.ActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.WindowConfiguration;
 import android.content.pm.ApplicationInfo;
@@ -40,6 +53,7 @@
 import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -51,11 +65,16 @@
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.window.BackEvent;
 import android.window.BackMotionEvent;
 import android.window.BackNavigationInfo;
 import android.window.IBackAnimationFinishedCallback;
 import android.window.IOnBackInvokedCallback;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -128,6 +147,8 @@
     private ShellBackAnimationRegistry mShellBackAnimationRegistry;
     private Rect mTouchableRegion;
 
+    private BackAnimationController.BackTransitionHandler mBackTransitionHandler;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -165,6 +186,8 @@
         mShellExecutor.flushAll();
         mTouchableRegion = new Rect(0, 0, 100, 100);
         mController.mTouchableArea.set(mTouchableRegion);
+        mBackTransitionHandler = mController.mBackTransitionHandler;
+        spyOn(mBackTransitionHandler);
     }
 
     private void createNavigationInfo(int backType,
@@ -606,6 +629,198 @@
                 mCrossTaskBackAnimation.getRunner());
     }
 
+    @Test
+    public void testCloseAsExpectTransition() {
+        final int openTaskId = 1;
+        final int closeTaskId = 2;
+        mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId);
+        final IBinder mockBinder = mock(IBinder.class);
+        final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class);
+        // Single close
+        final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN,
+                FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP);
+        final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE,
+                FLAG_BACK_GESTURE_ANIMATED);
+
+        TransitionInfo tInfo = createTransitionInfo(TRANSIT_CLOSE, open, close);
+        mBackTransitionHandler.mCloseTransitionRequested = true;
+        Transitions.TransitionFinishCallback callback =
+                mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+        verify(mBackTransitionHandler).handleCloseTransition(
+                eq(tInfo), eq(st), eq(ft), eq(callback));
+        mBackTransitionHandler.onAnimationFinished();
+        verify(callback).onTransitionFinished(any());
+        mBackTransitionHandler.mCloseTransitionRequested = false;
+
+        // PREPARE + CLOSE
+        tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
+        callback = mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+        verify(mBackTransitionHandler).handlePrepareTransition(
+                eq(tInfo), eq(st), eq(ft), eq(callback));
+        mBackTransitionHandler.mCloseTransitionRequested = true;
+        TransitionInfo tInfo2 = createTransitionInfo(TRANSIT_CLOSE, close);
+        Transitions.TransitionFinishCallback mergeCallback =
+                mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.mergeAnimation(
+                mock(IBinder.class), tInfo2, st, mock(IBinder.class), mergeCallback);
+        mBackTransitionHandler.onAnimationFinished();
+        verify(callback).onTransitionFinished(any());
+        verify(mergeCallback).onTransitionFinished(any());
+        mBackTransitionHandler.mCloseTransitionRequested = false;
+
+        // PREPARE contains close info
+        tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open, close);
+        callback = mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.mCloseTransitionRequested = true;
+        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+        verify(mBackTransitionHandler).handleCloseTransition(
+                eq(tInfo), eq(st), eq(ft), eq(callback));
+        mBackTransitionHandler.onAnimationFinished();
+        verify(callback).onTransitionFinished(any());
+        mBackTransitionHandler.mCloseTransitionRequested = false;
+
+        // PREPARE then Cancel
+        tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
+        callback = mock(Transitions.TransitionFinishCallback.class);
+        final TransitionRequestInfo requestInfo = new TransitionRequestInfo(
+                TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */,
+                null /* remoteTransition */);
+        mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
+        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+        verify(mBackTransitionHandler).handlePrepareTransition(
+                eq(tInfo), eq(st), eq(ft), eq(callback));
+        final TransitionInfo.Change openToClose = createAppChange(openTaskId, TRANSIT_CLOSE,
+                FLAG_BACK_GESTURE_ANIMATED);
+        tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose);
+        mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
+        mergeCallback = mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition,
+                tInfo2, st, mock(IBinder.class), mergeCallback);
+        assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty());
+        mBackTransitionHandler.onAnimationFinished();
+        verify(callback).onTransitionFinished(any());
+    }
+
+    @Test
+    public void testCancelUnexpectedTransition() {
+        final int openTaskId = 1;
+        final int closeTaskId = 2;
+        mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId);
+        final IBinder mockBinder = mock(IBinder.class);
+        final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class);
+        final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN,
+                FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP);
+        final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE,
+                FLAG_BACK_GESTURE_ANIMATED);
+
+        // Didn't trigger close transition
+        mBackTransitionHandler.mCloseTransitionRequested = false;
+        TransitionInfo prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION,
+                open, close);
+        final Transitions.TransitionFinishCallback callback =
+                mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.handleRequest(mockBinder, mock(TransitionRequestInfo.class));
+        boolean canHandle = mBackTransitionHandler.startAnimation(
+                mockBinder, prepareInfo, st, ft, callback);
+        assertFalse("Should not handle transition", canHandle);
+        assertNull(mBackTransitionHandler.mOnAnimationFinishCallback);
+
+        // Didn't trigger close transition, but receive close target.
+        final TransitionRequestInfo requestInfo = new TransitionRequestInfo(
+                TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */,
+                null /* remoteTransition */);
+        prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
+        final Transitions.TransitionFinishCallback callback2 =
+                mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
+        canHandle = mBackTransitionHandler.startAnimation(mockBinder,
+                prepareInfo, st, ft, callback2);
+        assertTrue("Handle prepare transition" , canHandle);
+        verify(mBackTransitionHandler).handlePrepareTransition(
+                eq(prepareInfo), eq(st), eq(ft), eq(callback2));
+        final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
+        Transitions.TransitionFinishCallback mergeCallback =
+                mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, ft,
+                mock(IBinder.class), mergeCallback);
+        verify(callback2).onTransitionFinished(any());
+        verify(mergeCallback, never()).onTransitionFinished(any());
+
+        // Didn't trigger close transition, but contains open target.
+        final int openTaskId2 = 3;
+        final Transitions.TransitionFinishCallback callback3 =
+                mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
+        canHandle = mBackTransitionHandler.startAnimation(
+                mockBinder, prepareInfo, st, ft, callback3);
+        assertTrue("Handle prepare transition" , canHandle);
+        verify(mBackTransitionHandler).handlePrepareTransition(
+                eq(prepareInfo), eq(st), eq(ft), eq(callback3));
+        final TransitionInfo.Change open2 = createAppChange(
+                openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
+        final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close);
+        mergeCallback = mock(Transitions.TransitionFinishCallback.class);
+        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, ft,
+                mock(IBinder.class), mergeCallback);
+        verify(callback3).onTransitionFinished(any());
+        verify(mergeCallback, never()).onTransitionFinished(any());
+    }
+
+    private RemoteAnimationTarget[] createAppAnimationTargets(int openTaskId, int closeTaskId) {
+        final RemoteAnimationTarget openT = createSingleAnimationTarget(openTaskId,
+                RemoteAnimationTarget.MODE_OPENING);
+        final RemoteAnimationTarget closeT = createSingleAnimationTarget(closeTaskId,
+                RemoteAnimationTarget.MODE_CLOSING);
+        return new RemoteAnimationTarget[]{openT, closeT};
+    }
+
+    private RemoteAnimationTarget createSingleAnimationTarget(int taskId, int mode) {
+        final Rect fakeR = new Rect();
+        final Point fakeP = new Point();
+        final ActivityManager.RunningTaskInfo openTaskInfo = new ActivityManager.RunningTaskInfo();
+        openTaskInfo.taskId = taskId;
+        openTaskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+        return new RemoteAnimationTarget(
+                taskId, mode, mock(SurfaceControl.class), false, fakeR, fakeR,
+                0, fakeP, fakeR, fakeR, new WindowConfiguration(), false,
+                mock(SurfaceControl.class), fakeR, openTaskInfo, false);
+    }
+    private TransitionInfo.Change createAppChange(
+            int taskId, @TransitionInfo.TransitionMode int mode,
+            @TransitionInfo.ChangeFlags int flags) {
+        final TransitionInfo.Change change;
+        SurfaceControl.Builder b = new SurfaceControl.Builder()
+                .setName("test task");
+        if (taskId != INVALID_TASK_ID) {
+            final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+            taskInfo.taskId = taskId;
+            taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+            change = new TransitionInfo.Change(
+                    taskInfo.token, b.build());
+            change.setTaskInfo(taskInfo);
+        } else {
+            change = new TransitionInfo.Change(
+                null, b.build());
+
+        }
+        change.setMode(mode);
+        change.setFlags(flags);
+        return change;
+    }
+
+    private static TransitionInfo createTransitionInfo(
+            @WindowManager.TransitionType int type, TransitionInfo.Change ... changes) {
+        final TransitionInfo info = new TransitionInfo(type, 0);
+        for (int i = 0; i < changes.length; ++i) {
+            info.addChange(changes[i]);
+        }
+        return info;
+    }
+
     private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
             throws RemoteException {
         final BackAnimationRunner animationRunner = spy(animation);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 7bb5449..a630cef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -641,6 +641,41 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+  fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+    setUpLandscapeDisplay()
+    val stableBounds = Rect()
+    displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+    setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+    val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false)
+
+    val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+    assertNotNull(wct, "should handle request")
+    val finalBounds = findBoundsChange(wct, freeformTask)
+    assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+      .isEqualTo(DesktopTaskPosition.BottomRight)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+  fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+    setUpLandscapeDisplay()
+    val stableBounds = Rect()
+    displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+    setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+    val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+
+    val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+    assertNull(wct, "should not handle request")
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
   fun addMoveToDesktopChanges_positionBottomRight() {
     setUpLandscapeDisplay()
     val stableBounds = Rect()
@@ -1784,6 +1819,19 @@
   }
 
   @Test
+  @EnableFlags(
+    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+  )
+  fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() {
+    val task = setUpFreeformTask()
+    markTaskHidden(task)
+
+    val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+    assertNull(result, "Should not handle request")
+  }
+
+  @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
   fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() {
@@ -2816,14 +2864,17 @@
 
   private fun setUpFreeformTask(
       displayId: Int = DEFAULT_DISPLAY,
-      bounds: Rect? = null
+      bounds: Rect? = null,
+      active: Boolean = true
   ): RunningTaskInfo {
     val task = createFreeformTask(displayId, bounds)
     val activityInfo = ActivityInfo()
     task.topActivityInfo = activityInfo
     whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
-    taskRepository.addActiveTask(displayId, task.taskId)
-    taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
+    if (active) {
+      taskRepository.addActiveTask(displayId, task.taskId)
+      taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
+    }
     taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
     runningTasks.add(task)
     return task
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index d8f395d..1691f07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -45,6 +45,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests for {@link DragResizeWindowGeometry}.
  *
@@ -57,11 +60,12 @@
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
     private static final int EDGE_RESIZE_THICKNESS = 12;
+    private static final int EDGE_RESIZE_HANDLE_INSET = 4;
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
-            TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE,
-            LARGE_CORNER_SIZE);
+            TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET,
+            FINE_CORNER_SIZE, LARGE_CORNER_SIZE);
     // Points in the edge resize handle. Note that coordinates start from the top left.
     private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
             -EDGE_RESIZE_THICKNESS / 2);
@@ -71,6 +75,16 @@
             TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2);
     private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
             TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
+    // Points in the inset of the task bounds still within the edge resize handle.
+    // Note that coordinates start from the top left.
+    private static final Point TOP_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2,
+            EDGE_RESIZE_HANDLE_INSET / 2);
+    private static final Point LEFT_INSET_POINT = new Point(EDGE_RESIZE_HANDLE_INSET / 2,
+            TASK_SIZE.getHeight() / 2);
+    private static final Point RIGHT_INSET_POINT = new Point(
+            TASK_SIZE.getWidth() - EDGE_RESIZE_HANDLE_INSET / 2, TASK_SIZE.getHeight() / 2);
+    private static final Point BOTTOM_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2,
+            TASK_SIZE.getHeight() - EDGE_RESIZE_HANDLE_INSET / 2);
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -85,18 +99,23 @@
                 .addEqualityGroup(
                         GEOMETRY,
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+                                EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET, FINE_CORNER_SIZE,
+                                LARGE_CORNER_SIZE))
                 .addEqualityGroup(
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
                 .addEqualityGroup(
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE,
                                 LARGE_CORNER_SIZE + 5),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE,
                                 LARGE_CORNER_SIZE + 5))
                 .testEquals();
     }
@@ -127,7 +146,7 @@
         assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
         // Vertically along the edge is not contained.
         assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS + 10)).isFalse();
     }
 
     private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
@@ -188,18 +207,18 @@
     }
 
     private void validateCtrlTypeForEdges(boolean isTouchscreen, boolean isEdgeResizePermitted) {
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                LEFT_EDGE_POINT.x, LEFT_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_LEFT : CTRL_TYPE_UNDEFINED);
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                TOP_EDGE_POINT.x, TOP_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                RIGHT_EDGE_POINT.x, RIGHT_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_RIGHT : CTRL_TYPE_UNDEFINED);
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                BOTTOM_EDGE_POINT.x, BOTTOM_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED);
+        List<Point> points = Arrays.asList(LEFT_EDGE_POINT, TOP_EDGE_POINT, RIGHT_EDGE_POINT,
+                BOTTOM_EDGE_POINT, LEFT_INSET_POINT, TOP_INSET_POINT, RIGHT_INSET_POINT,
+                BOTTOM_INSET_POINT);
+        List<Integer> expectedCtrlType = Arrays.asList(CTRL_TYPE_LEFT, CTRL_TYPE_TOP,
+                CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT, CTRL_TYPE_TOP, CTRL_TYPE_RIGHT,
+                CTRL_TYPE_BOTTOM);
+
+        for (int i = 0; i < points.size(); i++) {
+            assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
+                    points.get(i).x, points.get(i).y)).isEqualTo(
+                    isEdgeResizePermitted ? expectedCtrlType.get(i) : CTRL_TYPE_UNDEFINED);
+        }
     }
 
     @Test
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 3375e18c..ae63e19 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -57,6 +57,7 @@
 
   @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
+    method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 90ca92f..6c0f933 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -88,7 +88,7 @@
     boolean isReaderOptionEnabled();
     boolean isReaderOptionSupported();
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    boolean enableReaderOption(boolean enable);
+    boolean enableReaderOption(boolean enable, in String pkg);
     boolean isObserveModeSupported();
     boolean isObserveModeEnabled();
     boolean setObserveMode(boolean enabled, String pkg);
@@ -113,4 +113,5 @@
     void clearPreference();
     void setScreenState();
     void checkFirmware();
+    List<String> fetchActiveNfceeList();
 }
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index b36b705..525e2c5 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2011,7 +2011,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        return callServiceReturn(() ->  sService.enableReaderOption(enable), false);
+        return callServiceReturn(() ->
+                sService.enableReaderOption(enable, mContext.getPackageName()), false);
 
     }
 
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 2ec819c..204ba9f 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -26,6 +26,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -153,6 +155,19 @@
         NfcAdapter.callService(() -> NfcAdapter.sService.checkFirmware());
     }
 
+    /**
+     * Get the Active NFCEE (NFC Execution Environment) List
+     *
+     * @return List of activated secure elements on success
+     *         which can contain "eSE" and "UICC", otherwise empty list.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    public List<String> getActiveNfceeList() {
+        return NfcAdapter.callServiceReturn(() ->
+            NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>());
+    }
+
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
         @Override
         public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 0d124e8..2b3862f 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -119,3 +119,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "audio_sharing_hysteresis_mode_fix"
+    namespace: "cross_device_experiences"
+    description: "Gates whether to enable fix for hysteresis mode"
+    bug: "355222285"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 2f4b2ef..4b141e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -220,6 +220,16 @@
         return getTriggerDescription();
     }
 
+    /**
+     * Returns an icon "key" that is guaranteed to be different if the icon is different. Note that
+     * the inverse is not true, i.e. two keys can be different and the icon still be visually the
+     * same.
+     */
+    @NonNull
+    public String getIconKey() {
+        return mRule.getType() + ":" + mRule.getPackageName() + ":" + mRule.getIconResId();
+    }
+
     @NonNull
     public ListenableFuture<Drawable> getIcon(@NonNull Context context,
             @NonNull ZenIconLoader iconLoader) {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index c881e07..c60eb61 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -36,7 +36,7 @@
         "androidx.coordinatorlayout_coordinatorlayout",
         "androidx.core_core",
         "androidx.preference_preference",
-        "androidx.viewpager_viewpager",
+        "androidx.viewpager2_viewpager2",
         "com_android_systemui_flags_lib",
         "SettingsLibDisplayUtils",
         "SettingsLibSettingsTheme",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
index 462c90b..e1b6e63 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
@@ -22,7 +22,7 @@
       android:orientation="horizontal">
 
     <ImageButton
-        android:id="@+id/menu_prev_button"
+        android:id="@+id/menu_left_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_weight="1"
@@ -38,7 +38,7 @@
         android:background="?android:attr/listDivider"/>
 
     <ImageButton
-        android:id="@+id/menu_next_button"
+        android:id="@+id/menu_right_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_weight="1"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml
index c198443..c0aa1b3 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml
@@ -2,7 +2,7 @@
 <GridView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/gridview"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="match_parent"
     android:horizontalSpacing="@dimen/a11ymenu_grid_layout_margin"
     android:listSelector="@android:color/transparent"
     android:numColumns="3"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
index 28a633e..6be7655 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
@@ -6,18 +6,16 @@
     android:background="@drawable/view_background"
     >
   <LinearLayout
-      android:layout_width="@dimen/row_width"
+      android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
 
-    <androidx.viewpager.widget.ViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/view_pager"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingTop="@dimen/table_margin_top"
         android:paddingBottom="@dimen/a11ymenu_layout_margin"
-        android:paddingLeft="@dimen/a11ymenu_layout_margin"
-        android:paddingRight="@dimen/a11ymenu_layout_margin"
         android:layout_gravity="center"
         android:gravity="center"
         />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
index 20c63df..78fbf01 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
@@ -16,7 +16,11 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
+import static android.view.View.LAYOUT_DIRECTION_LTR;
+
+import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.text.TextUtils;
 import android.view.TouchDelegate;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -37,46 +41,61 @@
     public interface A11yMenuFooterCallBack {
 
         /** Calls back when user clicks the left button. */
-        void onLeftButtonClicked();
+        void onNextButtonClicked();
 
         /** Calls back when user clicks the right button. */
-        void onRightButtonClicked();
+        void onPreviousButtonClicked();
     }
 
     private final FooterButtonClickListener mFooterButtonClickListener;
 
-    private ImageButton mPreviousPageBtn;
-    private ImageButton mNextPageBtn;
+    private ImageButton mPageLeftBtn;
+    private ImageButton mPageRightBtn;
     private View mTopListDivider;
     private View mBottomListDivider;
     private final A11yMenuFooterCallBack mCallBack;
+    private final ViewGroup mMenuLayout;
+    private int mRightToLeftDirection = LAYOUT_DIRECTION_LTR;
 
     public A11yMenuFooter(ViewGroup menuLayout, A11yMenuFooterCallBack callBack) {
         this.mCallBack = callBack;
         mFooterButtonClickListener = new FooterButtonClickListener();
         configureFooterLayout(menuLayout);
+        mMenuLayout = menuLayout;
     }
 
     public @Nullable ImageButton getPreviousPageBtn() {
-        return mPreviousPageBtn;
+        return mRightToLeftDirection == LAYOUT_DIRECTION_LTR
+                ? mPageLeftBtn : mPageRightBtn;
     }
 
     public @Nullable ImageButton getNextPageBtn() {
-        return mNextPageBtn;
+        return mRightToLeftDirection == LAYOUT_DIRECTION_LTR
+                ? mPageRightBtn : mPageLeftBtn;
+    }
+
+    /** Sets right to left direction of footer. */
+    public void updateRightToLeftDirection(Configuration configuration) {
+        mRightToLeftDirection = TextUtils.getLayoutDirectionFromLocale(
+                configuration.getLocales().get(0));
+        getPreviousPageBtn().setContentDescription(mMenuLayout.getResources().getString(
+                R.string.previous_button_content_description));
+        getNextPageBtn().setContentDescription(mMenuLayout.getResources().getString(
+                R.string.next_button_content_description));
     }
 
     private void configureFooterLayout(ViewGroup menuLayout) {
         ViewGroup footerContainer = menuLayout.findViewById(R.id.footerlayout);
         footerContainer.setVisibility(View.VISIBLE);
 
-        mPreviousPageBtn = menuLayout.findViewById(R.id.menu_prev_button);
-        mNextPageBtn = menuLayout.findViewById(R.id.menu_next_button);
+        mPageLeftBtn = menuLayout.findViewById(R.id.menu_left_button);
+        mPageRightBtn = menuLayout.findViewById(R.id.menu_right_button);
         mTopListDivider = menuLayout.findViewById(R.id.top_listDivider);
         mBottomListDivider = menuLayout.findViewById(R.id.bottom_listDivider);
 
         // Registers listeners for footer buttons.
-        setListener(mPreviousPageBtn);
-        setListener(mNextPageBtn);
+        setListener(mPageLeftBtn);
+        setListener(mPageRightBtn);
 
         menuLayout
                 .getViewTreeObserver()
@@ -85,8 +104,8 @@
                             @Override
                             public void onGlobalLayout() {
                                 menuLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                                expandBtnTouchArea(mPreviousPageBtn, menuLayout);
-                                expandBtnTouchArea(mNextPageBtn, (View) mNextPageBtn.getParent());
+                                expandBtnTouchArea(mPageLeftBtn, menuLayout);
+                                expandBtnTouchArea(mPageRightBtn, (View) mPageRightBtn.getParent());
                             }
                         });
     }
@@ -115,10 +134,10 @@
     private class FooterButtonClickListener implements OnClickListener {
         @Override
         public void onClick(View view) {
-            if (view.getId() == R.id.menu_prev_button) {
-                mCallBack.onLeftButtonClicked();
-            } else if (view.getId() == R.id.menu_next_button) {
-                mCallBack.onRightButtonClicked();
+            if (view.getId() == getPreviousPageBtn().getId()) {
+                mCallBack.onPreviousButtonClicked();
+            } else if (view.getId() == getNextPageBtn().getId()) {
+                mCallBack.onNextButtonClicked();
             }
         }
     }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 6bea30f..de3c472 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -145,13 +145,14 @@
         final Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
         final Context context = mService.createDisplayContext(display).createWindowContext(
                 TYPE_ACCESSIBILITY_OVERLAY, null);
-        mLayout = new FrameLayout(context);
+        mLayout = new A11yMenuFrameLayout(context);
         updateLayoutPosition();
         inflateLayoutAndSetOnTouchListener(mLayout, context);
         mA11yMenuViewPager = new A11yMenuViewPager(mService, context);
         mA11yMenuViewPager.configureViewPagerAndFooter(mLayout, createShortcutList(), pageIndex);
         mWindowManager.addView(mLayout, mLayoutParameter);
         mLayout.setVisibility(lastVisibilityState);
+        mA11yMenuViewPager.updateFooterState();
 
         return mLayout;
     }
@@ -393,4 +394,16 @@
                         }
                     }), timeoutDurationMs);
     }
+
+    private class A11yMenuFrameLayout extends FrameLayout {
+        A11yMenuFrameLayout(@NonNull Context context) {
+            super(context);
+        }
+
+        @Override
+        public void dispatchConfigurationChanged(Configuration newConfig) {
+            super.dispatchConfigurationChanged(newConfig);
+            mA11yMenuViewPager.mA11yMenuFooter.updateRightToLeftDirection(newConfig);
+        }
+    }
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index b969017..08bbf19 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -20,7 +20,6 @@
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.util.DisplayMetrics;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -29,7 +28,7 @@
 import android.view.WindowMetrics;
 import android.widget.GridView;
 
-import androidx.viewpager.widget.ViewPager;
+import androidx.viewpager2.widget.ViewPager2;
 
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
 import com.android.systemui.accessibility.accessibilitymenu.R;
@@ -133,9 +132,9 @@
      * The pager widget, which handles animation and allows swiping horizontally to access previous
      * and next gridView pages.
      */
-    protected ViewPager mViewPager;
+    protected ViewPager2 mViewPager;
 
-    private ViewPagerAdapter<GridView> mViewPagerAdapter;
+    private ViewPagerAdapter mViewPagerAdapter;
     private final List<GridView> mGridPageList = new ArrayList<>();
 
     /** The footer, which provides buttons to switch between pages */
@@ -169,6 +168,8 @@
         initViewPager();
         initChildPage();
         mA11yMenuFooter = new A11yMenuFooter(a11yMenuLayout, mFooterCallbacks);
+        mA11yMenuFooter.updateRightToLeftDirection(
+                a11yMenuLayout.getResources().getConfiguration());
         updateFooterState();
         registerOnGlobalLayoutListener();
         goToPage(pageIndex);
@@ -177,18 +178,12 @@
     /** Initializes viewPager and its adapter. */
     private void initViewPager() {
         mViewPager = mA11yMenuLayout.findViewById(R.id.view_pager);
-        mViewPagerAdapter = new ViewPagerAdapter<>();
+        mViewPagerAdapter = new ViewPagerAdapter(mService);
+        mViewPager.setOffscreenPageLimit(2);
         mViewPager.setAdapter(mViewPagerAdapter);
         mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
-        mViewPager.addOnPageChangeListener(
-                new ViewPager.OnPageChangeListener() {
-                    @Override
-                    public void onPageScrollStateChanged(int state) {}
-
-                    @Override
-                    public void onPageScrolled(
-                            int position, float positionOffset, int positionOffsetPixels) {}
-
+        mViewPager.registerOnPageChangeCallback(
+                new ViewPager2.OnPageChangeCallback() {
                     @Override
                     public void onPageSelected(int position) {
                         updateFooterState();
@@ -206,31 +201,14 @@
             mGridPageList.clear();
         }
 
-        // Generate pages by calculating # of items per grid.
-        for (List<A11yMenuShortcut> page : GridViewParams.generateShortcutSubLists(
-                GridViewParams.getGridItemCount(mService), mA11yMenuShortcutList)
-        ) {
-            addGridPage(page);
-        }
-
-        mViewPagerAdapter.set(mGridPageList);
-    }
-
-    private void addGridPage(List<A11yMenuShortcut> shortcutDataListInPage) {
-        LayoutInflater inflater = LayoutInflater.from(mDisplayContext);
-        View view = inflater.inflate(R.layout.grid_view, null);
-        GridView gridView = view.findViewById(R.id.gridview);
-        A11yMenuAdapter adapter = new A11yMenuAdapter(
-                mService, mDisplayContext, shortcutDataListInPage);
-        gridView.setNumColumns(GridViewParams.getGridColumnCount(mService));
-        gridView.setAdapter(adapter);
-        mGridPageList.add(gridView);
+        mViewPagerAdapter.set(GridViewParams.generateShortcutSubLists(
+                GridViewParams.getGridItemCount(mService), mA11yMenuShortcutList));
     }
 
     /** Updates footer's state by index of current page in view pager. */
-    private void updateFooterState() {
+    public void updateFooterState() {
         int currentPage = mViewPager.getCurrentItem();
-        int lastPage = mViewPager.getAdapter().getCount() - 1;
+        int lastPage = mViewPager.getAdapter().getItemCount() - 1;
         mA11yMenuFooter.getPreviousPageBtn().setEnabled(currentPage > 0);
         mA11yMenuFooter.getNextPageBtn().setEnabled(currentPage < lastPage);
     }
@@ -239,7 +217,7 @@
         if (mViewPager == null) {
             return;
         }
-        if ((pageIndex >= 0) && (pageIndex < mViewPager.getAdapter().getCount())) {
+        if ((pageIndex >= 0) && (pageIndex < mViewPager.getAdapter().getItemCount())) {
             mViewPager.setCurrentItem(pageIndex);
         }
     }
@@ -341,7 +319,7 @@
     protected A11yMenuFooterCallBack mFooterCallbacks =
             new A11yMenuFooterCallBack() {
                 @Override
-                public void onLeftButtonClicked() {
+                public void onPreviousButtonClicked() {
                     // Moves to previous page.
                     int targetPage = mViewPager.getCurrentItem() - 1;
                     goToPage(targetPage);
@@ -349,7 +327,7 @@
                 }
 
                 @Override
-                public void onRightButtonClicked() {
+                public void onNextButtonClicked() {
                     // Moves to next page.
                     int targetPage = mViewPager.getCurrentItem() + 1;
                     goToPage(targetPage);
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
index 5670d72..43ec956 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
@@ -16,55 +16,64 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.GridView;
 
-import androidx.viewpager.widget.PagerAdapter;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.R;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
 
 import java.util.List;
 
 /** The pager adapter, which provides the pages to the view pager widget. */
-class ViewPagerAdapter<T extends View> extends PagerAdapter {
+class ViewPagerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
 
-    /** The widget list in each page of view pager. */
-    private List<T> mWidgetList;
+    /** List of shortcuts, split into sub lists per page */
+    private List<List<A11yMenuShortcut>> mShortcutList;
+    private final AccessibilityMenuService mService;
 
-    ViewPagerAdapter() {}
+    ViewPagerAdapter(AccessibilityMenuService service) {
+        mService = service;
+    }
 
-    public void set(List<T> tList) {
-        mWidgetList = tList;
+    public void set(List<List<A11yMenuShortcut>> tList) {
+        mShortcutList = tList;
         notifyDataSetChanged();
     }
 
+    @NonNull
     @Override
-    public int getCount() {
-        if (mWidgetList == null) {
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        View view = inflater.inflate(R.layout.grid_view, parent, false);
+        return new MenuViewHolder(view.findViewById(R.id.gridview));
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        A11yMenuAdapter adapter = new A11yMenuAdapter(
+                mService, holder.itemView.getContext(), mShortcutList.get(position));
+        GridView gridView = (GridView) holder.itemView;
+        gridView.setNumColumns(A11yMenuViewPager.GridViewParams.getGridColumnCount(mService));
+        gridView.setAdapter(adapter);
+    }
+
+    @Override
+    public int getItemCount() {
+        if (mShortcutList == null) {
             return 0;
         }
-        return mWidgetList.size();
+        return mShortcutList.size();
     }
 
-    @Override
-    public int getItemPosition(Object object) {
-        return POSITION_NONE;
-    }
-
-    @Override
-    public boolean isViewFromObject(View view, Object object) {
-        return view == object;
-    }
-
-    @Override
-    public Object instantiateItem(ViewGroup container, int position) {
-        if (mWidgetList == null) {
-            return null;
+    static class MenuViewHolder extends RecyclerView.ViewHolder {
+        MenuViewHolder(View itemView) {
+            super(itemView);
         }
-        container.addView(mWidgetList.get(position));
-        return mWidgetList.get(position);
     }
-
-    @Override
-    public void destroyItem(ViewGroup container, int position, Object object) {
-        container.removeView((View) object);
-    }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8860452..5251246 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -4,6 +4,16 @@
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
+    name: "add_black_background_for_window_magnifier"
+    namespace: "accessibility"
+    description: "Set the background for SurfaceView in window magnification black."
+    bug: "299981434"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "create_windowless_window_magnifier"
     namespace: "accessibility"
     description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index cdbac33..5632e30 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -281,13 +281,6 @@
 }
 
 flag {
-    name: "qs_new_pipeline"
-    namespace: "systemui"
-    description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
-    bug: "241772429"
-}
-
-flag {
    name: "qs_new_tiles"
    namespace: "systemui"
    description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
@@ -998,6 +991,16 @@
 }
 
 flag {
+  name: "communal_widget_trampoline_fix"
+  namespace: "systemui"
+  description: "fixes activity starts caused by non-activity trampolines from widgets."
+  bug: "350468769"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "app_clips_backlinks"
   namespace: "systemui"
   description: "Enables Backlinks improvement feature in App Clips"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index a18b460..a5f8057 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -21,8 +21,7 @@
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.material3.ButtonColors
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Icon
@@ -32,7 +31,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
 
@@ -42,11 +40,10 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     colors: ButtonColors = filledButtonColors(),
-    verticalPadding: Dp = DefaultPlatformButtonVerticalPadding,
     content: @Composable RowScope.() -> Unit,
 ) {
     androidx.compose.material3.Button(
-        modifier = modifier.padding(vertical = verticalPadding).height(36.dp),
+        modifier = modifier.heightIn(min = 36.dp),
         colors = colors,
         contentPadding = ButtonPaddings,
         onClick = onClick,
@@ -63,11 +60,10 @@
     enabled: Boolean = true,
     colors: ButtonColors = outlineButtonColors(),
     border: BorderStroke? = outlineButtonBorder(),
-    verticalPadding: Dp = DefaultPlatformButtonVerticalPadding,
     content: @Composable RowScope.() -> Unit,
 ) {
     androidx.compose.material3.OutlinedButton(
-        modifier = modifier.padding(vertical = verticalPadding).height(36.dp),
+        modifier = modifier.heightIn(min = 36.dp),
         enabled = enabled,
         colors = colors,
         border = border,
@@ -118,7 +114,6 @@
     }
 }
 
-private val DefaultPlatformButtonVerticalPadding = 6.dp
 private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
 
 @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index f655ac1..d164eab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -95,7 +95,7 @@
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModel
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.MessageViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
@@ -114,7 +114,7 @@
 
 @Composable
 fun BouncerContent(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
@@ -128,7 +128,7 @@
 @VisibleForTesting
 fun BouncerContent(
     layout: BouncerSceneLayout,
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier
 ) {
@@ -173,7 +173,7 @@
  */
 @Composable
 private fun StandardLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val isHeightExpanded =
@@ -235,7 +235,7 @@
  */
 @Composable
 private fun SplitLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
@@ -326,7 +326,7 @@
  */
 @Composable
 private fun BesideUserSwitcherLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val layoutDirection = LocalLayoutDirection.current
@@ -461,7 +461,7 @@
 /** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
 @Composable
 private fun BelowUserSwitcherLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     Column(
@@ -506,7 +506,7 @@
 
 @Composable
 private fun FoldAware(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     aboveFold: @Composable BoxScope.() -> Unit,
     belowFold: @Composable BoxScope.() -> Unit,
     modifier: Modifier = Modifier,
@@ -649,7 +649,7 @@
  */
 @Composable
 private fun OutputArea(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -677,7 +677,7 @@
  */
 @Composable
 private fun InputArea(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     pinButtonRowVerticalSpacing: Dp,
     centerPatternDotsVertically: Boolean,
     modifier: Modifier = Modifier,
@@ -706,7 +706,7 @@
 
 @Composable
 private fun ActionArea(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val actionButton: BouncerActionButtonModel? by
@@ -774,7 +774,7 @@
 
 @Composable
 private fun Dialog(
-    bouncerViewModel: BouncerViewModel,
+    bouncerViewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
 ) {
     val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle()
@@ -803,7 +803,7 @@
 /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
 @Composable
 private fun UserSwitcher(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     if (!viewModel.isUserSwitcherVisible) {
@@ -884,7 +884,7 @@
 @Composable
 private fun UserSwitcherDropdownMenu(
     isExpanded: Boolean,
-    items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
+    items: List<BouncerSceneContentViewModel.UserSwitcherDropdownItemViewModel>,
     onDismissed: () -> Unit,
 ) {
     val context = LocalContext.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 9fd30b4..3a46882 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -27,9 +27,11 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import javax.inject.Inject
@@ -51,23 +53,37 @@
 class BouncerScene
 @Inject
 constructor(
-    private val viewModel: BouncerViewModel,
+    private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
+    private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
     private val dialogFactory: BouncerDialogFactory,
 ) : ComposableScene {
     override val key = Scenes.Bouncer
 
+    private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
+
+    override suspend fun activate() {
+        actionsViewModel.activate()
+    }
 
     @Composable
     override fun SceneScope.Content(
         modifier: Modifier,
-    ) = BouncerScene(viewModel, dialogFactory, modifier)
+    ) =
+        BouncerScene(
+            viewModel = rememberViewModel { contentViewModelFactory.create() },
+            dialogFactory = dialogFactory,
+            modifier = modifier,
+        )
 }
 
 @Composable
 private fun SceneScope.BouncerScene(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
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 12edd04..9db8bf1 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
@@ -92,7 +92,8 @@
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQS
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneContentViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
@@ -120,8 +121,9 @@
 constructor(
     private val shadeSession: SaveableSession,
     private val notificationStackScrollView: Lazy<NotificationScrollView>,
-    private val viewModel: QuickSettingsSceneViewModel,
     private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
+    private val actionsViewModelFactory: QuickSettingsSceneActionsViewModel.Factory,
+    private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
@@ -130,8 +132,16 @@
 ) : ComposableScene {
     override val key = Scenes.QuickSettings
 
+    private val actionsViewModel: QuickSettingsSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
+
+    override suspend fun activate() {
+        actionsViewModel.activate()
+    }
 
     @Composable
     override fun SceneScope.Content(
@@ -139,7 +149,7 @@
     ) {
         QuickSettingsScene(
             notificationStackScrollView = notificationStackScrollView.get(),
-            viewModel = viewModel,
+            viewModelFactory = contentViewModelFactory,
             notificationsPlaceholderViewModel =
                 rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
             createTintedIconManager = tintedIconManagerFactory::create,
@@ -156,7 +166,7 @@
 @Composable
 private fun SceneScope.QuickSettingsScene(
     notificationStackScrollView: NotificationScrollView,
-    viewModel: QuickSettingsSceneViewModel,
+    viewModelFactory: QuickSettingsSceneContentViewModel.Factory,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -168,6 +178,7 @@
 ) {
     val cutoutLocation = LocalDisplayCutout.current.location
 
+    val viewModel = rememberViewModel { viewModelFactory.create() }
     val brightnessMirrorViewModel = rememberViewModel {
         viewModel.brightnessMirrorViewModelFactory.create()
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 68a6c98..920c234 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -17,7 +17,6 @@
 package com.android.compose.animation.scene
 
 import com.android.compose.animation.scene.content.state.TransitionState
-import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 
@@ -63,47 +62,30 @@
             if (transitionState.toScene == target) {
                 // The user is currently swiping to [target] but didn't release their pointer yet:
                 // animate the progress to `1`.
-
                 check(transitionState.fromScene == transitionState.currentScene)
-                val progress = transitionState.progress
-                if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) {
-                    // The transition is already finished (progress ~= 1): no need to animate. We
-                    // finish the current transition early to make sure that the current state
-                    // change is committed.
-                    layoutState.finishTransition(transitionState, target)
-                    null
-                } else {
-                    // The transition is in progress: start the canned animation at the same
-                    // progress as it was in.
-                    animateToScene(
-                        layoutState,
-                        target,
-                        transitionKey,
-                        isInitiatedByUserInput,
-                        replacedTransition = transitionState,
-                    )
-                }
+
+                // The transition is in progress: start the canned animation at the same
+                // progress as it was in.
+                animateToScene(
+                    layoutState,
+                    target,
+                    transitionKey,
+                    isInitiatedByUserInput,
+                    replacedTransition = transitionState,
+                )
             } else if (transitionState.fromScene == target) {
                 // There is a transition from [target] to another scene: simply animate the same
                 // transition progress to `0`.
                 check(transitionState.toScene == transitionState.currentScene)
 
-                val progress = transitionState.progress
-                if (progress.absoluteValue < ProgressVisibilityThreshold) {
-                    // The transition is at progress ~= 0: no need to animate.We finish the current
-                    // transition early to make sure that the current state change is committed.
-                    layoutState.finishTransition(transitionState, target)
-                    null
-                } else {
-                    animateToScene(
-                        layoutState,
-                        target,
-                        transitionKey,
-                        isInitiatedByUserInput,
-                        reversed = true,
-                        replacedTransition = transitionState,
-                    )
-                }
+                animateToScene(
+                    layoutState,
+                    target,
+                    transitionKey,
+                    isInitiatedByUserInput,
+                    reversed = true,
+                    replacedTransition = transitionState,
+                )
             } else {
                 // Generic interruption; the current transition is neither from or to [target].
                 val interruptionResult =
@@ -185,7 +167,7 @@
         oneOffAnimation = oneOffAnimation,
         targetProgress = targetProgress,
         startTransition = { layoutState.startTransition(transition, chain) },
-        finishTransition = { layoutState.finishTransition(transition, targetScene) },
+        finishTransition = { layoutState.finishTransition(transition) },
     )
 
     return transition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 712fe6b..9c3896b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -777,7 +777,8 @@
 
     fun snapToScene(scene: SceneKey) {
         cancelOffsetAnimation()
-        layoutState.finishTransition(this, idleScene = scene)
+        check(currentScene == scene)
+        layoutState.finishTransition(this)
     }
 
     override fun finish(): Job {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index fe16ef751..2fbdf7c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -129,7 +129,7 @@
                 try {
                     animatable.animateTo(targetProgress)
                 } finally {
-                    state.finishTransition(this@PredictiveBackTransition, scene)
+                    state.finishTransition(this@PredictiveBackTransition)
                 }
             }
             .also { animationJob = it }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index a6c6a80..44f5964f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -167,8 +167,6 @@
     override val transitionState: TransitionState
         get() = transitionStates.last()
 
-    private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
-
     override val currentTransitions: List<TransitionState.Transition>
         get() {
             if (transitionStates.last() is TransitionState.Idle) {
@@ -180,12 +178,8 @@
             }
         }
 
-    /**
-     * The mapping of transitions that are finished, i.e. for which [finishTransition] was called,
-     * to their idle scene.
-     */
-    @VisibleForTesting
-    internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>()
+    /** The transitions that are finished, i.e. for which [finishTransition] was called. */
+    @VisibleForTesting internal val finishedTransitions = mutableSetOf<TransitionState.Transition>()
 
     internal fun checkThread() {
         val current = Thread.currentThread()
@@ -261,7 +255,7 @@
         }
 
         // Handle transition links.
-        cancelActiveTransitionLinks()
+        currentTransition?.let { cancelActiveTransitionLinks(it) }
         setupTransitionLinks(transition)
 
         if (!enableInterruptions) {
@@ -289,8 +283,7 @@
 
                     // Force finish all transitions.
                     while (currentTransitions.isNotEmpty()) {
-                        val transition = transitionStates[0] as TransitionState.Transition
-                        finishTransition(transition, transition.currentScene)
+                        finishTransition(transitionStates[0] as TransitionState.Transition)
                     }
 
                     // We finished all transitions, so we are now idle. We remove this state so that
@@ -328,18 +321,17 @@
         )
     }
 
-    private fun cancelActiveTransitionLinks() {
-        for ((link, linkedTransition) in activeTransitionLinks) {
-            link.target.finishTransition(linkedTransition, linkedTransition.currentScene)
+    private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) {
+        transition.activeTransitionLinks.forEach { (link, linkedTransition) ->
+            link.target.finishTransition(linkedTransition)
         }
-        activeTransitionLinks.clear()
+        transition.activeTransitionLinks.clear()
     }
 
-    private fun setupTransitionLinks(transitionState: TransitionState) {
-        if (transitionState !is TransitionState.Transition) return
+    private fun setupTransitionLinks(transition: TransitionState.Transition) {
         stateLinks.fastForEach { stateLink ->
             val matchingLinks =
-                stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) }
+                stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
             if (matchingLinks.isEmpty()) return@fastForEach
             if (matchingLinks.size > 1) error("More than one link matched.")
 
@@ -350,31 +342,27 @@
 
             val linkedTransition =
                 LinkedTransition(
-                    originalTransition = transitionState,
+                    originalTransition = transition,
                     fromScene = targetCurrentScene,
                     toScene = matchingLink.targetTo,
                     key = matchingLink.targetTransitionKey,
                 )
 
             stateLink.target.startTransition(linkedTransition)
-            activeTransitionLinks[stateLink] = linkedTransition
+            transition.activeTransitionLinks[stateLink] = linkedTransition
         }
     }
 
     /**
-     * Notify that [transition] was finished and that we should settle to [idleScene]. This will do
-     * nothing if [transition] was interrupted since it was started.
+     * Notify that [transition] was finished and that it settled to its
+     * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was
+     * interrupted since it was started.
      */
-    internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+    internal fun finishTransition(transition: TransitionState.Transition) {
         checkThread()
 
-        val existingIdleScene = finishedTransitions[transition]
-        if (existingIdleScene != null) {
+        if (finishedTransitions.contains(transition)) {
             // This transition was already finished.
-            check(idleScene == existingIdleScene) {
-                "Transition $transition was finished multiple times with different " +
-                    "idleScene ($existingIdleScene != $idleScene)"
-            }
             return
         }
 
@@ -386,15 +374,15 @@
 
         check(transitionStates.fastAll { it is TransitionState.Transition })
 
-        // Mark this transition as finished and save the scene it is settling at.
-        finishedTransitions[transition] = idleScene
+        // Mark this transition as finished.
+        finishedTransitions.add(transition)
 
         // Finish all linked transitions.
-        finishActiveTransitionLinks(idleScene)
+        finishActiveTransitionLinks(transition)
 
-        // Keep a reference to the idle scene of the last removed transition, in case we remove all
-        // transitions and should settle to Idle.
-        var lastRemovedIdleScene: SceneKey? = null
+        // Keep a reference to the last transition, in case we remove all transitions and should
+        // settle to Idle.
+        val lastTransition = transitionStates.last()
 
         // Remove all first n finished transitions.
         var i = 0
@@ -407,14 +395,14 @@
             }
 
             // Remove the transition from the set of finished transitions.
-            lastRemovedIdleScene = finishedTransitions.remove(t)
+            finishedTransitions.remove(t)
             i++
         }
 
         // If all transitions are finished, we are idle.
         if (i == nStates) {
             check(finishedTransitions.isEmpty())
-            this.transitionStates = listOf(TransitionState.Idle(checkNotNull(lastRemovedIdleScene)))
+            this.transitionStates = listOf(TransitionState.Idle(lastTransition.currentScene))
         } else if (i > 0) {
             this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates)
         }
@@ -426,28 +414,18 @@
         // Force finish all transitions.
         while (currentTransitions.isNotEmpty()) {
             val transition = transitionStates[0] as TransitionState.Transition
-            finishTransition(transition, transition.currentScene)
+            finishTransition(transition)
         }
 
         check(transitionStates.size == 1)
         transitionStates = listOf(TransitionState.Idle(scene))
     }
 
-    private fun finishActiveTransitionLinks(idleScene: SceneKey) {
-        val previousTransition = this.transitionState as? TransitionState.Transition ?: return
-        for ((link, linkedTransition) in activeTransitionLinks) {
-            if (previousTransition.fromScene == idleScene) {
-                // The transition ended by arriving at the fromScene, move link to Idle(fromScene).
-                link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
-            } else if (previousTransition.toScene == idleScene) {
-                // The transition ended by arriving at the toScene, move link to Idle(toScene).
-                link.target.finishTransition(linkedTransition, linkedTransition.toScene)
-            } else {
-                // The transition was interrupted by something else, we reset to initial state.
-                link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
-            }
+    private fun finishActiveTransitionLinks(transition: TransitionState.Transition) {
+        for ((link, linkedTransition) in transition.activeTransitionLinks) {
+            link.target.finishTransition(linkedTransition)
         }
-        activeTransitionLinks.clear()
+        transition.activeTransitionLinks.clear()
     }
 
     /**
@@ -465,31 +443,21 @@
 
         fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
 
-        fun finishAllTransitions(lastTransitionIdleScene: SceneKey) {
+        fun finishAllTransitions() {
             // Force finish all transitions.
             while (currentTransitions.isNotEmpty()) {
-                val transition = transitionStates[0] as TransitionState.Transition
-                val idleScene =
-                    if (transitionStates.size == 1) {
-                        lastTransitionIdleScene
-                    } else {
-                        transition.currentScene
-                    }
-
-                finishTransition(transition, idleScene)
+                finishTransition(transitionStates[0] as TransitionState.Transition)
             }
         }
 
-        return when {
-            isProgressCloseTo(0f) -> {
-                finishAllTransitions(transition.fromScene)
-                true
-            }
-            isProgressCloseTo(1f) -> {
-                finishAllTransitions(transition.toScene)
-                true
-            }
-            else -> false
+        val shouldSnap =
+            (isProgressCloseTo(0f) && transition.currentScene == transition.fromScene) ||
+                (isProgressCloseTo(1f) && transition.currentScene == transition.toScene)
+        return if (shouldSnap) {
+            finishAllTransitions()
+            true
+        } else {
+            false
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
index add3934..0bd676b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
@@ -29,6 +29,8 @@
 import com.android.compose.animation.scene.TransformationSpec
 import com.android.compose.animation.scene.TransformationSpecImpl
 import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
@@ -110,6 +112,9 @@
          */
         private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
 
+        /** The map of active links that connects this transition to other transitions. */
+        internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
         init {
             check(fromContent != toContent)
             check(
@@ -131,7 +136,7 @@
          *   animation is complete or cancel it to snap the animation. Calling [finish] multiple
          *   times will return the same [Job].
          */
-        abstract fun finish(): Job
+        internal abstract fun finish(): Job
 
         /**
          * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 75f44ff..60cefb0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -1380,8 +1380,8 @@
 
         // Manually finish the transition.
         rule.runOnUiThread {
-            state.finishTransition(aToB, SceneB)
-            state.finishTransition(bToC, SceneC)
+            state.finishTransition(aToB)
+            state.finishTransition(bToC)
         }
         rule.waitForIdle()
         assertThat(state.transitionState).isIdle()
@@ -1482,7 +1482,7 @@
 
         // Manually finish A => B so only B => C is remaining.
         bToCInterruptionProgress = 0f
-        rule.runOnUiThread { state.finishTransition(aToB, SceneB) }
+        rule.runOnUiThread { state.finishTransition(aToB) }
         rule
             .onNode(isElement(TestElements.Foo, SceneB))
             .assertPositionInRootIsEqualTo(offsetInB.x, offsetInB.y)
@@ -2002,6 +2002,7 @@
             transition(
                 from = SceneB,
                 to = SceneC,
+                current = { SceneB },
                 progress = { 0f },
                 interruptionProgress = { interruptionProgress },
                 onFinish = neverFinish(),
@@ -2018,8 +2019,8 @@
         // 100%. We should be at (20dp, 20dp), unless the interruption deltas have not been
         // correctly cleaned.
         rule.runOnUiThread {
-            state.finishTransition(aToB, idleScene = SceneB)
-            state.finishTransition(bToC, idleScene = SceneB)
+            state.finishTransition(aToB)
+            state.finishTransition(bToC)
             state.startTransition(
                 transition(
                     from = SceneB,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 6b417ee..c8ac580 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,7 +18,9 @@
 
 import android.util.Log
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestScenes.SceneA
@@ -110,16 +112,6 @@
     }
 
     @Test
-    fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
-        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
-
-        // Progress is 0f, so we don't animate at all and directly snap back to A.
-        assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
-    }
-
-    @Test
     fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
 
@@ -180,7 +172,7 @@
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
 
-        childState.finishTransition(childTransition, SceneB)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
     }
@@ -217,7 +209,7 @@
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
         assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
 
-        childState.finishTransition(childTransition, SceneB)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
         assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
@@ -241,13 +233,13 @@
     fun linkedTransition_reverseTransitionIsNotLinked() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = transition(SceneB, SceneA)
+        val childTransition = transition(SceneB, SceneA, current = { SceneB })
 
         childState.startTransition(childTransition)
         assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
 
-        childState.finishTransition(childTransition, SceneB)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
@@ -256,27 +248,15 @@
     fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = transition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB, current = { SceneA })
         childState.startTransition(childTransition)
 
-        childState.finishTransition(childTransition, SceneA)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
     @Test
-    fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
-        val (parentState, childState) = setupLinkedStates()
-
-        val childTransition = transition(SceneA, SceneB)
-        childState.startTransition(childTransition)
-
-        childState.finishTransition(childTransition, SceneD)
-        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
-        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-    }
-
-    @Test
     fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest {
         val (parentState, childState) = setupLinkedStates()
 
@@ -295,7 +275,7 @@
         childState.startTransition(childTransition)
         parentState.startTransition(parentTransition)
 
-        childState.finishTransition(childTransition, SceneB)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(parentTransition)
     }
@@ -341,7 +321,9 @@
     @Test
     fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.2f }))
+        state.startTransition(
+            transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f })
+        )
         assertThat(state.isTransitioning()).isTrue()
 
         // Ignore the request if the progress is not close to 0 or 1, using the threshold.
@@ -399,6 +381,31 @@
     }
 
     @Test
+    fun snapToIdleIfClose_closeButNotCurrentScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        var progress by mutableStateOf(0f)
+        var currentScene by mutableStateOf(SceneB)
+        state.startTransition(
+            transition(
+                from = SceneA,
+                to = SceneB,
+                current = { currentScene },
+                progress = { progress }
+            )
+        )
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Ignore the request if we are close to a scene that is not the current scene
+        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+        assertThat(state.isTransitioning()).isTrue()
+
+        progress = 1f
+        currentScene = SceneA
+        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+        assertThat(state.isTransitioning()).isTrue()
+    }
+
+    @Test
     fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
         val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
         val childTransition = transition(SceneA, SceneB)
@@ -407,7 +414,7 @@
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
 
-        childState.finishTransition(childTransition, SceneB)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
     }
@@ -417,13 +424,13 @@
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
 
-        val childTransition = transition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB, current = { SceneA })
 
         childState.startTransition(childTransition)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
 
-        childState.finishTransition(childTransition, SceneA)
+        childState.finishTransition(childTransition)
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
@@ -595,12 +602,12 @@
 
         // Mark bToC as finished. The list of current transitions does not change because aToB is
         // still not marked as finished.
-        state.finishTransition(bToC, idleScene = bToC.currentScene)
-        assertThat(state.finishedTransitions).containsExactly(bToC, bToC.currentScene)
+        state.finishTransition(bToC)
+        assertThat(state.finishedTransitions).containsExactly(bToC)
         assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder()
 
         // Mark aToB as finished. This will remove both aToB and bToC from the list of transitions.
-        state.finishTransition(aToB, idleScene = aToB.currentScene)
+        state.finishTransition(aToB)
         assertThat(state.finishedTransitions).isEmpty()
         assertThat(state.currentTransitions).containsExactly(cToA).inOrder()
     }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index 91bd7e1..e4e4108 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -29,7 +29,7 @@
 fun transition(
     from: SceneKey,
     to: SceneKey,
-    current: () -> SceneKey = { from },
+    current: () -> SceneKey = { to },
     progress: () -> Float = { 0f },
     progressVelocity: () -> Float = { 0f },
     previewProgress: () -> Float = { 0f },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index c9fa671..deef652 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -22,14 +22,14 @@
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -39,17 +39,16 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val underTest by lazy {
-        PinBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = testScope.backgroundScope,
-            interactor = bouncerInteractor,
+    private val underTest =
+        kosmos.pinBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true),
-            simBouncerInteractor = kosmos.simBouncerInteractor,
-            authenticationMethod = AuthenticationMethodModel.Pin,
             onIntentionalUserInput = {},
+            authenticationMethod = AuthenticationMethodModel.Pin,
         )
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index 4f5d0e5..b83ab7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -87,6 +88,7 @@
             intArrayOf(ignoreHelpMessageId)
         )
         underTest = kosmos.bouncerMessageViewModel
+        underTest.activateIn(testScope)
         overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
         kosmos.fakeSystemPropertiesHelper.set(
             DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
new file mode 100644
index 0000000..a86a0c0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.testKosmos
+import com.android.systemui.truth.containsEntriesExactly
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class BouncerSceneActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: BouncerSceneActionsViewModel
+
+    @Before
+    fun setUp() {
+        kosmos.sceneContainerStartable.start()
+        underTest = kosmos.bouncerSceneActionsViewModel
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun actions() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
+            runCurrent()
+
+            kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
+            runCurrent()
+
+            assertThat(actions)
+                .containsEntriesExactly(
+                    Back to UserActionResult(Scenes.QuickSettings),
+                    Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
+                )
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
similarity index 88%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
index ccddc9c..9bddcd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
@@ -18,10 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -38,11 +34,9 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
-import com.android.systemui.truth.containsEntriesExactly
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,17 +56,18 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableSceneContainer
-class BouncerViewModelTest : SysuiTestCase() {
+class BouncerSceneContentViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private lateinit var underTest: BouncerViewModel
+    private lateinit var underTest: BouncerSceneContentViewModel
 
     @Before
     fun setUp() {
         kosmos.sceneContainerStartable.start()
-        underTest = kosmos.bouncerViewModel
+        underTest = kosmos.bouncerSceneContentViewModel
+        underTest.activateIn(testScope)
     }
 
     @Test
@@ -201,23 +196,6 @@
             assertThat(isFoldSplitRequired).isTrue()
         }
 
-    @Test
-    fun destinationScenes() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
-            runCurrent()
-
-            kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
-            runCurrent()
-
-            assertThat(destinationScenes)
-                .containsEntriesExactly(
-                    Back to UserActionResult(Scenes.QuickSettings),
-                    Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
-                )
-        }
-
     private fun authMethodsToTest(): List<AuthenticationMethodModel> {
         return listOf(None, Pin, Password, Pattern, Sim)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index a09189e..492543f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -44,7 +45,6 @@
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -68,12 +68,8 @@
     private val isInputEnabled = MutableStateFlow(true)
 
     private val underTest =
-        PasswordBouncerViewModel(
-            viewModelScope = testScope.backgroundScope,
-            isInputEnabled = isInputEnabled.asStateFlow(),
-            interactor = bouncerInteractor,
-            inputMethodInteractor = inputMethodInteractor,
-            selectedUserInteractor = selectedUserInteractor,
+        kosmos.passwordBouncerViewModelFactory.create(
+            isInputEnabled = isInputEnabled,
             onIntentionalUserInput = {},
         )
 
@@ -81,6 +77,7 @@
     fun setUp() {
         overrideResource(R.string.keyguard_enter_your_password, ENTER_YOUR_PASSWORD)
         overrideResource(R.string.kg_wrong_password, WRONG_PASSWORD)
+        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 14d3634..7c773a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -26,9 +26,9 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -54,17 +54,12 @@
     private val testScope = kosmos.testScope
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
-    private val underTest by lazy {
-        PatternBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = testScope.backgroundScope,
-            interactor = bouncerInteractor,
+    private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+    private val underTest =
+        kosmos.patternBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
             onIntentionalUserInput = {},
         )
-    }
 
     private val containerSize = 90 // px
     private val dotSize = 30 // px
@@ -73,6 +68,7 @@
     fun setUp() {
         overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN)
         overrideResource(R.string.kg_wrong_pattern, WRONG_PATTERN)
+        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 89bafb9..8d82e97 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -31,10 +31,9 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -44,7 +43,6 @@
 import kotlin.random.nextInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -62,24 +60,18 @@
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private lateinit var underTest: PinBouncerViewModel
+    private val underTest =
+        kosmos.pinBouncerViewModelFactory.create(
+            isInputEnabled = MutableStateFlow(true),
+            onIntentionalUserInput = {},
+            authenticationMethod = AuthenticationMethodModel.Pin,
+        )
 
     @Before
     fun setUp() {
-        underTest =
-            PinBouncerViewModel(
-                applicationContext = context,
-                viewModelScope = testScope.backgroundScope,
-                interactor = bouncerInteractor,
-                isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                simBouncerInteractor = kosmos.simBouncerInteractor,
-                authenticationMethod = AuthenticationMethodModel.Pin,
-                onIntentionalUserInput = {},
-            )
-
         overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN)
         overrideResource(R.string.kg_wrong_pin, WRONG_PIN)
+        underTest.activateIn(testScope)
     }
 
     @Test
@@ -96,14 +88,10 @@
     fun simBouncerViewModel_simAreaIsVisible() =
         testScope.runTest {
             val underTest =
-                PinBouncerViewModel(
-                    applicationContext = context,
-                    viewModelScope = testScope.backgroundScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = kosmos.simBouncerInteractor,
-                    authenticationMethod = AuthenticationMethodModel.Sim,
+                kosmos.pinBouncerViewModelFactory.create(
+                    isInputEnabled = MutableStateFlow(true),
                     onIntentionalUserInput = {},
+                    authenticationMethod = AuthenticationMethodModel.Sim,
                 )
 
             assertThat(underTest.isSimAreaVisible).isTrue()
@@ -125,14 +113,10 @@
     fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() =
         testScope.runTest {
             val underTest =
-                PinBouncerViewModel(
-                    applicationContext = context,
-                    viewModelScope = testScope.backgroundScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = kosmos.simBouncerInteractor,
-                    authenticationMethod = AuthenticationMethodModel.Sim,
+                kosmos.pinBouncerViewModelFactory.create(
+                    isInputEnabled = MutableStateFlow(true),
                     onIntentionalUserInput = {},
+                    authenticationMethod = AuthenticationMethodModel.Pin,
                 )
             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -355,6 +339,7 @@
                 AuthenticationMethodModel.Pin
             )
             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+            runCurrent()
 
             underTest.onPinButtonClicked(1)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
new file mode 100644
index 0000000..b3ffc71
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.usage.UsageEvents
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.usagestats.data.repository.fakeUsageStatsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.shared.system.taskStackChangeListeners
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetTrampolineInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val activityStarter = kosmos.activityStarter
+    private val usageStatsRepository = kosmos.fakeUsageStatsRepository
+    private val taskStackChangeListeners = kosmos.taskStackChangeListeners
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val userTracker = kosmos.fakeUserTracker
+    private val systemClock = kosmos.fakeSystemClock
+
+    private val underTest = kosmos.widgetTrampolineInteractor
+
+    @Before
+    fun setUp() {
+        userTracker.set(listOf(MAIN_USER), 0)
+        systemClock.setCurrentTimeMillis(testScope.currentTime)
+    }
+
+    @Test
+    fun testNewTaskStartsWhileOnHub_triggersUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+            moveTaskToFront()
+
+            verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN)
+            moveTaskToFront()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testNewTaskStartsAfterTimeout_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            advanceTime(2.seconds)
+            moveTaskToFront()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testActivityResumedWhileOnHub_triggersUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testActivityResumedAfterExitingHub_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN)
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testActivityDestroyed_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            addActivityEvent(UsageEvents.Event.ACTIVITY_DESTROYED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testMultipleActivityEvents_triggersUnlockOnlyOnce() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(10.milliseconds)
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter, times(1)).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    private fun TestScope.advanceTime(duration: Duration) {
+        systemClock.advanceTime(duration.inWholeMilliseconds)
+        advanceTimeBy(duration)
+    }
+
+    private fun TestScope.addActivityEvent(type: Int) {
+        usageStatsRepository.addEvent(
+            instanceId = 1,
+            user = MAIN_USER.userHandle,
+            packageName = "pkg.test",
+            timestamp = systemClock.currentTimeMillis(),
+            type = type,
+        )
+        runCurrent()
+    }
+
+    private fun TestScope.moveTaskToFront() {
+        taskStackChangeListeners.listenerImpl.onTaskMovedToFront(mock<RunningTaskInfo>())
+        runCurrent()
+    }
+
+    private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
+        keyguardTransitionRepository.sendTransitionSteps(
+            listOf(
+                TransitionStep(
+                    from = from,
+                    to = to,
+                    value = 0.1f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "test",
+                ),
+                TransitionStep(
+                    from = from,
+                    to = to,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "test",
+                ),
+            ),
+            testScope
+        )
+        runCurrent()
+    }
+
+    private companion object {
+        val MAIN_USER: UserInfo = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 023de52..400f736 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -27,7 +27,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.widgetTrampolineInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
@@ -67,9 +69,11 @@
         with(kosmos) {
             underTest =
                 WidgetInteractionHandler(
+                    applicationScope = applicationCoroutineScope,
                     activityStarter = activityStarter,
                     communalSceneInteractor = communalSceneInteractor,
                     logBuffer = logcatLogBuffer(),
+                    widgetTrampolineInteractor = widgetTrampolineInteractor,
                 )
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 409c551..5ec566b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -49,6 +50,24 @@
     val underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel
 
     @Test
+    fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor(alpha = { 0.5f })
+            val alpha by collectLastValue(underTest.lockscreenAlpha(viewState))
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f))
+            assertThat(alpha).isEqualTo(0.5f)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isIn(Range.open(0.5f, 1f))
+
+            keyguardTransitionRepository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
     fun deviceEntryParentViewAlpha() =
         testScope.runTest {
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 3146318..8995f46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -24,7 +24,6 @@
 import android.service.quicksettings.Tile
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
 import com.android.systemui.Flags.FLAG_QS_NEW_TILES
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -107,7 +106,6 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
         mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
index e8ad038..00c7204 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -20,7 +20,6 @@
 import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -98,8 +97,6 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
         with(kosmos) {
             restoreReconciliationInteractor.start()
             autoAddInteractor.init(kosmos.currentTilesInteractor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
index dffd0d7..6bcaea4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -20,7 +20,6 @@
 import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -59,6 +58,7 @@
     // Getter here so it can change when there is a managed profile.
     private val workTileAvailable: Boolean
         get() = hasManagedProfile()
+
     private val currentUser: Int
         get() = kosmos.userTracker.userId
 
@@ -67,8 +67,6 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
-
         kosmos.qsTileFactory = FakeQSFactory(::tileCreator)
         kosmos.restoreReconciliationInteractor.start()
         kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor)
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/QuickSettingsSceneActionsViewModelTest.kt
similarity index 72%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt
index 0363808..f26a9db 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/QuickSettingsSceneActionsViewModelTest.kt
@@ -36,11 +36,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.data.repository.mediaFilterRepository
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.qs.FooterActionsController
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneBackInteractor
@@ -49,41 +45,29 @@
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
-import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
 import com.android.systemui.testKosmos
-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.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @EnableSceneContainer
-class QuickSettingsSceneViewModelTest : SysuiTestCase() {
+class QuickSettingsSceneActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
-    private val footerActionsViewModel = mock<FooterActionsViewModel>()
-    private val footerActionsViewModelFactory =
-        mock<FooterActionsViewModel.Factory> {
-            whenever(create(any())).thenReturn(footerActionsViewModel)
-        }
-    private val footerActionsController = mock<FooterActionsController>()
 
     private val sceneInteractor = kosmos.sceneInteractor
     private val sceneBackInteractor = kosmos.sceneBackInteractor
     private val sceneContainerStartable = kosmos.sceneContainerStartable
 
-    private lateinit var underTest: QuickSettingsSceneViewModel
+    private lateinit var underTest: QuickSettingsSceneActionsViewModel
 
     @Before
     fun setUp() {
@@ -91,22 +75,18 @@
 
         sceneContainerStartable.start()
         underTest =
-            QuickSettingsSceneViewModel(
-                brightnessMirrorViewModelFactory = kosmos.brightnessMirrorViewModelFactory,
-                shadeHeaderViewModelFactory = kosmos.shadeHeaderViewModelFactory,
+            QuickSettingsSceneActionsViewModel(
                 qsSceneAdapter = qsFlexiglassAdapter,
-                footerActionsViewModelFactory = footerActionsViewModelFactory,
-                footerActionsController = footerActionsController,
                 sceneBackInteractor = sceneBackInteractor,
-                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
             )
+        underTest.activateIn(testScope)
     }
 
     @Test
     fun destinations_whenNotCustomizing_unlocked() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             qsFlexiglassAdapter.setCustomizing(false)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -116,7 +96,7 @@
                 SuccessFingerprintAuthenticationStatus(0, true)
             )
 
-            assertThat(destinations)
+            assertThat(actions)
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
@@ -135,7 +115,7 @@
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
             qsFlexiglassAdapter.setCustomizing(false)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
 
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val backScene by collectLastValue(sceneBackInteractor.backScene)
@@ -145,7 +125,7 @@
             assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(backScene).isEqualTo(Scenes.Lockscreen)
 
-            assertThat(destinations)
+            assertThat(actions)
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Lockscreen),
@@ -164,7 +144,7 @@
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
             qsFlexiglassAdapter.setCustomizing(false)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
 
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val backScene by collectLastValue(sceneBackInteractor.backScene)
@@ -176,7 +156,7 @@
 
             assertThat(currentScene).isEqualTo(Scenes.Gone)
             assertThat(backScene).isNull()
-            assertThat(destinations)
+            assertThat(actions)
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
@@ -194,7 +174,7 @@
     fun destinations_whenNotCustomizing_authMethodSwipe_lockscreenNotDismissed() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             qsFlexiglassAdapter.setCustomizing(false)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -202,7 +182,7 @@
                 AuthenticationMethodModel.None
             )
 
-            assertThat(destinations)
+            assertThat(actions)
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
@@ -220,17 +200,17 @@
     fun destinations_whenCustomizing_noDestinations() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             qsFlexiglassAdapter.setCustomizing(true)
 
-            assertThat(destinations).isEmpty()
+            assertThat(actions).isEmpty()
         }
 
     @Test
     fun destinations_whenNotCustomizing_inSplitShade_unlocked() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, true)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             qsFlexiglassAdapter.setCustomizing(false)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -240,7 +220,7 @@
                 SuccessFingerprintAuthenticationStatus(0, true)
             )
 
-            assertThat(destinations)
+            assertThat(actions)
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
@@ -258,49 +238,9 @@
     fun destinations_whenCustomizing_inSplitShade_noDestinations() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, true)
-            val destinations by collectLastValue(underTest.destinationScenes)
+            val actions by collectLastValue(underTest.actions)
             qsFlexiglassAdapter.setCustomizing(true)
 
-            assertThat(destinations).isEmpty()
-        }
-
-    @Test
-    fun gettingViewModelInitializesControllerOnlyOnce() {
-        underTest.getFooterActionsViewModel(mock())
-        underTest.getFooterActionsViewModel(mock())
-
-        verify(footerActionsController, times(1)).init()
-    }
-
-    @Test
-    fun addAndRemoveMedia_mediaVisibilityIsUpdated() =
-        testScope.runTest {
-            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
-            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
-            val userMedia = MediaData(active = true)
-
-            assertThat(isMediaVisible).isFalse()
-
-            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
-
-            assertThat(isMediaVisible).isTrue()
-
-            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
-
-            assertThat(isMediaVisible).isFalse()
-        }
-
-    @Test
-    fun addInactiveMedia_mediaVisibilityIsUpdated() =
-        testScope.runTest {
-            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
-            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
-            val userMedia = MediaData(active = false)
-
-            assertThat(isMediaVisible).isFalse()
-
-            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
-
-            assertThat(isMediaVisible).isTrue()
+            assertThat(actions).isEmpty()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
new file mode 100644
index 0000000..9563538
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import android.testing.TestableLooper.RunWithLooper
+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.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
+import com.android.systemui.testKosmos
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@EnableSceneContainer
+class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
+    private val footerActionsViewModel = mock<FooterActionsViewModel>()
+    private val footerActionsViewModelFactory =
+        mock<FooterActionsViewModel.Factory> {
+            whenever(create(any())).thenReturn(footerActionsViewModel)
+        }
+    private val footerActionsController = mock<FooterActionsController>()
+
+    private val sceneContainerStartable = kosmos.sceneContainerStartable
+
+    private lateinit var underTest: QuickSettingsSceneContentViewModel
+
+    @Before
+    fun setUp() {
+        kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
+
+        sceneContainerStartable.start()
+        underTest =
+            QuickSettingsSceneContentViewModel(
+                brightnessMirrorViewModelFactory = kosmos.brightnessMirrorViewModelFactory,
+                shadeHeaderViewModelFactory = kosmos.shadeHeaderViewModelFactory,
+                qsSceneAdapter = qsFlexiglassAdapter,
+                footerActionsViewModelFactory = footerActionsViewModelFactory,
+                footerActionsController = footerActionsController,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
+            )
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun gettingViewModelInitializesControllerOnlyOnce() {
+        underTest.getFooterActionsViewModel(mock())
+        underTest.getFooterActionsViewModel(mock())
+
+        verify(footerActionsController, times(1)).init()
+    }
+
+    @Test
+    fun addAndRemoveMedia_mediaVisibilityIsUpdated() =
+        testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = true)
+
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
+
+            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+            assertThat(isMediaVisible).isFalse()
+        }
+
+    @Test
+    fun addInactiveMedia_mediaVisibilityIsUpdated() =
+        testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = false)
+
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
+        }
+}
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 66e45ab..aee3ce0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -36,10 +36,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
@@ -133,13 +133,14 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                motionEventHandlerReceiver = {},
             )
             .apply { setTransitionState(transitionState) }
     }
 
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
-    private lateinit var bouncerViewModel: BouncerViewModel
+    private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
 
     private val lockscreenSceneActionsViewModel by lazy {
         LockscreenSceneActionsViewModel(
@@ -187,7 +188,7 @@
         }
 
         bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
-        bouncerViewModel = kosmos.bouncerViewModel
+        bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
 
         shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
         shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
@@ -198,6 +199,8 @@
         lockscreenSceneActionsViewModel.activateIn(testScope)
         shadeSceneContentViewModel.activateIn(testScope)
         shadeSceneActionsViewModel.activateIn(testScope)
+        bouncerSceneContentViewModel.activateIn(testScope)
+        sceneContainerViewModel.activateIn(testScope)
 
         assertWithMessage("Initial scene key mismatch!")
             .that(sceneContainerViewModel.currentScene.value)
@@ -397,7 +400,7 @@
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -417,7 +420,7 @@
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible during call")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -568,7 +571,7 @@
         bouncerSceneJob =
             if (to == Scenes.Bouncer) {
                 testScope.backgroundScope.launch {
-                    bouncerViewModel.authMethodViewModel.collect {
+                    bouncerSceneContentViewModel.authMethodViewModel.collect {
                         // Do nothing. Need this to turn this otherwise cold flow, hot.
                     }
                 }
@@ -644,7 +647,8 @@
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
-        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+        val authMethodViewModel by
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -672,7 +676,8 @@
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
-        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+        val authMethodViewModel by
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -719,7 +724,7 @@
 
     /** Emulates the dismissal of the IME (soft keyboard). */
     private fun TestScope.dismissIme() {
-        (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
+        (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
             it.onImeDismissed()
             runCurrent()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index ea95aab..f85823a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.scene.ui.viewmodel
 
 import android.view.MotionEvent
@@ -25,6 +27,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -37,6 +40,8 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -57,6 +62,9 @@
 
     private lateinit var underTest: SceneContainerViewModel
 
+    private lateinit var activationJob: Job
+    private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
+
     @Before
     fun setUp() {
         underTest =
@@ -64,10 +72,28 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                motionEventHandlerReceiver = { motionEventHandler ->
+                    this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
+                },
             )
+        activationJob = Job()
+        underTest.activateIn(testScope, activationJob)
     }
 
     @Test
+    fun activate_setsMotionEventHandler() =
+        testScope.runTest { assertThat(motionEventHandler).isNotNull() }
+
+    @Test
+    fun deactivate_clearsMotionEventHandler() =
+        testScope.runTest {
+            activationJob.cancel()
+            runCurrent()
+
+            assertThat(motionEventHandler).isNull()
+        }
+
+    @Test
     fun isVisible() =
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 57fd9ea..d069c01 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -71,7 +71,7 @@
     <string name="kg_prompt_after_dpm_lock" msgid="6002804765868345917">"لمزيد من الأمان، تم قفل الجهاز وفقًا لسياسة العمل."</string>
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"يجب إدخال رقم التعريف الشخصي بعد إلغاء الفتح الذكي."</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"يجب إدخال كلمة المرور بعد إلغاء الفتح الذكي."</string>
-    <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء التأمين."</string>
+    <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء الفتح الذكي."</string>
     <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"سيتم تثبيت التحديث عندما لا يكون الجهاز قيد الاستخدام."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"‏يجب تعزيز الأمان. لم يُستخدَم رقم PIN لبعض الوقت."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"يجب تعزيز الأمان. لم تستخدَم كلمة المرور لبعض الوقت."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index fd90d08..cf2057c 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"Неверный PIN-код"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Неверный PIN-код."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Повторите попытку или используйте отпечаток пальца."</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан."</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Лицо не распознано."</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Повторите попытку или введите PIN-код."</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Повторите попытку или введите пароль."</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 63ad41a..13cd2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -53,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.animation.PhysicsAnimator.SpringConfig;
@@ -63,13 +64,6 @@
 public class SwipeHelper implements Gefingerpoken, Dumpable {
     static final String TAG = "com.android.systemui.SwipeHelper";
     private static final boolean DEBUG_INVALIDATE = false;
-    private static final boolean CONSTRAIN_SWIPE = true;
-    private static final boolean FADE_OUT_DURING_SWIPE = true;
-    private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
-
-    public static final int X = 0;
-    public static final int Y = 1;
-
     private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
     private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
     private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
@@ -171,10 +165,6 @@
         mPagingTouchSlop = pagingTouchSlop;
     }
 
-    public void setDisableHardwareLayers(boolean disableHwLayers) {
-        mDisableHwLayers = disableHwLayers;
-    }
-
     private float getPos(MotionEvent ev) {
         return ev.getX();
     }
@@ -253,13 +243,14 @@
             float translation) {
         float swipeProgress = getSwipeProgressForOffset(animView, translation);
         if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
-            if (FADE_OUT_DURING_SWIPE && dismissable) {
-                if (!mDisableHwLayers) {
-                    if (swipeProgress != 0f && swipeProgress != 1f) {
-                        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                    } else {
-                        animView.setLayerType(View.LAYER_TYPE_NONE, null);
-                    }
+            if (dismissable
+                    || (NotificationContentAlphaOptimization.isEnabled() && translation == 0)) {
+                // We need to reset the content alpha even when the view is not dismissible (eg.
+                //  when Guts is visible)
+                if (swipeProgress != 0f && swipeProgress != 1f) {
+                    animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                } else {
+                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
                 }
                 updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
             }
@@ -436,9 +427,7 @@
             duration = fixedDuration;
         }
 
-        if (!mDisableHwLayers) {
-            animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        }
+        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
@@ -493,9 +482,7 @@
                 if (endAction != null) {
                     endAction.accept(mCancelled);
                 }
-                if (!mDisableHwLayers) {
-                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
-                }
+                animView.setLayerType(View.LAYER_TYPE_NONE, null);
                 onDismissChildWithAnimationFinished();
             }
         });
@@ -612,7 +599,11 @@
      * view is being animated to dismiss or snap.
      */
     public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
-        updateSwipeProgressFromOffset(animView, canBeDismissed, value);
+        updateSwipeProgressFromOffset(
+                animView,
+                /* dismissable= */ canBeDismissed,
+                /* translation= */ value
+        );
     }
 
     private void snapChildInstantly(final View view) {
@@ -689,7 +680,7 @@
                     } else {
                         // don't let items that can't be dismissed be dragged more than
                         // maxScrollDistance
-                        if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
+                        if (!mCallback.canChildBeDismissedInDirection(
                                 mTouchedView,
                                 delta > 0)) {
                             float size = getSize(mTouchedView);
@@ -761,8 +752,7 @@
 
     protected boolean swipedFarEnough() {
         float translation = getTranslation(mTouchedView);
-        return DISMISS_IF_SWIPED_FAR_ENOUGH
-                && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
+        return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
                 mTouchedView);
     }
 
@@ -822,9 +812,18 @@
     }
 
     public void forceResetSwipeState(@NonNull View view) {
-        if (view.getTranslationX() == 0) return;
+        if (view.getTranslationX() == 0
+                && (!NotificationContentAlphaOptimization.isEnabled() || view.getAlpha() == 1f)
+        ) {
+            // Don't do anything when translation is 0 and alpha is 1
+            return;
+        }
         setTranslation(view, 0);
-        updateSwipeProgressFromOffset(view, /* dismissable= */ true, 0);
+        updateSwipeProgressFromOffset(
+                view,
+                /* dismissable= */ true,
+                /* translation= */ 0
+        );
     }
 
     /** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */
@@ -893,7 +892,6 @@
         pw.append("mTranslation=").println(mTranslation);
         pw.append("mCanCurrViewBeDimissed=").println(mCanCurrViewBeDimissed);
         pw.append("mMenuRowIntercepting=").println(mMenuRowIntercepting);
-        pw.append("mDisableHwLayers=").println(mDisableHwLayers);
         pw.append("mDismissPendingMap: ").println(mDismissPendingMap.size());
         if (!mDismissPendingMap.isEmpty()) {
             mDismissPendingMap.forEach((view, animator) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 3828f9f..f0483a5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -112,6 +112,7 @@
             MagnificationConstants.SCALE_MAX_VALUE);
     private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
     private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
+    private static final float[] COLOR_BLACK_ARRAY = {0f, 0f, 0f};
     private final SparseArray<Float> mMagnificationSizeScaleOptions = new SparseArray<>();
 
     private final Context mContext;
@@ -1019,6 +1020,11 @@
         if (!mMirrorSurface.isValid()) {
             return;
         }
+        // Set the surface of the SurfaceView to black to avoid users seeing the contents below the
+        // magnifier when the mirrored surface has an alpha less than 1.
+        if (Flags.addBlackBackgroundForWindowMagnifier()) {
+            mTransaction.setColor(mMirrorSurfaceView.getSurfaceControl(), COLOR_BLACK_ARRAY);
+        }
         mTransaction.show(mMirrorSurface)
                 .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
         modifyWindowMagnification(false);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
index aebc50f..3410782 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -18,8 +18,6 @@
 
 import android.app.AlertDialog
 import android.content.Context
-import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModelModule
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -27,13 +25,7 @@
 import dagger.Module
 import dagger.Provides
 
-@Module(
-    includes =
-        [
-            BouncerViewModelModule::class,
-            BouncerMessageViewModelModule::class,
-        ],
-)
+@Module
 interface BouncerViewModule {
     /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
     @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 78811a9..ad93a25 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -9,7 +9,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -40,7 +40,7 @@
 @Inject
 constructor(
     val legacyInteractor: PrimaryBouncerInteractor,
-    val viewModel: BouncerViewModel,
+    val viewModelFactory: BouncerSceneContentViewModel.Factory,
     val dialogFactory: BouncerDialogFactory,
     val authenticationInteractor: AuthenticationInteractor,
     val viewMediatorCallback: ViewMediatorCallback?,
@@ -65,7 +65,7 @@
             ComposeBouncerViewBinder.bind(
                 view,
                 deps.legacyInteractor,
-                deps.viewModel,
+                deps.viewModelFactory,
                 deps.dialogFactory,
                 deps.authenticationInteractor,
                 deps.selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index eaca276..c1f7d59 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -14,7 +14,8 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.composable.BouncerContent
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.flow.collectLatest
@@ -25,7 +26,7 @@
     fun bind(
         view: ViewGroup,
         legacyInteractor: PrimaryBouncerInteractor,
-        viewModel: BouncerViewModel,
+        viewModelFactory: BouncerSceneContentViewModel.Factory,
         dialogFactory: BouncerDialogFactory,
         authenticationInteractor: AuthenticationInteractor,
         selectedUserInteractor: SelectedUserInteractor,
@@ -48,7 +49,14 @@
                                     this@repeatWhenAttached.lifecycle
                             }
                         )
-                        setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+                        setContent {
+                            PlatformTheme {
+                                BouncerContent(
+                                    rememberViewModel { viewModelFactory.create() },
+                                    dialogFactory,
+                                )
+                            }
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 4fbf735..e7dd974 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -17,17 +17,18 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.annotation.StringRes
+import com.android.app.tracing.coroutines.flow.collectLatest
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.lifecycle.SysUiViewModel
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.receiveAsFlow
 
 sealed class AuthMethodBouncerViewModel(
-    protected val viewModelScope: CoroutineScope,
     protected val interactor: BouncerInteractor,
 
     /**
@@ -37,7 +38,7 @@
      * being able to attempt to unlock the device.
      */
     val isInputEnabled: StateFlow<Boolean>,
-) {
+) : SysUiViewModel() {
 
     private val _animateFailure = MutableStateFlow(false)
     /**
@@ -57,6 +58,29 @@
      */
     @get:StringRes abstract val lockoutMessageId: Int
 
+    private val authenticationRequests = Channel<AuthenticationRequest>(Channel.BUFFERED)
+
+    override suspend fun onActivated() {
+        authenticationRequests.receiveAsFlow().collectLatest { request ->
+            if (!isInputEnabled.value) {
+                return@collectLatest
+            }
+
+            val authenticationResult =
+                interactor.authenticate(
+                    input = request.input,
+                    tryAutoConfirm = request.useAutoConfirm,
+                )
+
+            if (authenticationResult == AuthenticationResult.SKIPPED && request.useAutoConfirm) {
+                return@collectLatest
+            }
+
+            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
+            clearInput()
+        }
+    }
+
     /**
      * Notifies that the UI has been hidden from the user (after any transitions have completed).
      */
@@ -92,14 +116,11 @@
         input: List<Any> = getInput(),
         useAutoConfirm: Boolean = false,
     ) {
-        viewModelScope.launch {
-            val authenticationResult = interactor.authenticate(input, useAutoConfirm)
-            if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) {
-                return@launch
-            }
-            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
-
-            clearInput()
-        }
+        authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
     }
+
+    private data class AuthenticationRequest(
+        val input: List<Any>,
+        val useAutoConfirm: Boolean,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 31479f1..c3215b4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
 import com.android.systemui.bouncer.shared.model.primaryMessage
 import com.android.systemui.bouncer.shared.model.secondaryMessage
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
@@ -39,19 +38,19 @@
 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.time.SystemClock
-import dagger.Module
-import dagger.Provides
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlin.math.ceil
 import kotlin.math.max
 import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -65,20 +64,21 @@
 
 /** Holds UI state for the 2-line status message shown on the bouncer. */
 @OptIn(ExperimentalCoroutinesApi::class)
-class BouncerMessageViewModel(
+class BouncerMessageViewModel
+@AssistedInject
+constructor(
     @Application private val applicationContext: Context,
-    @Application private val applicationScope: CoroutineScope,
     private val bouncerInteractor: BouncerInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
     private val authenticationInteractor: AuthenticationInteractor,
-    selectedUser: Flow<UserViewModel>,
+    private val userSwitcherViewModel: UserSwitcherViewModel,
     private val clock: SystemClock,
     private val biometricMessageInteractor: BiometricMessageInteractor,
     private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
-    flags: ComposeBouncerFlags,
-) {
+    private val flags: ComposeBouncerFlags,
+) : SysUiViewModel() {
     /**
      * A message shown when the user has attempted the wrong credential too many times and now must
      * wait a while before attempting to authenticate again.
@@ -94,6 +94,25 @@
     /** The user-facing message to show in the bouncer. */
     val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null)
 
+    override suspend fun onActivated() {
+        if (!flags.isComposeBouncerOrSceneContainerEnabled()) {
+            return
+        }
+
+        coroutineScope {
+            launch {
+                // Update the lockout countdown whenever the selected user is switched.
+                userSwitcherViewModel.selectedUser.collect { startLockoutCountdown() }
+            }
+
+            launch { defaultBouncerMessageInitializer() }
+            launch { listenForSimBouncerEvents() }
+            launch { listenForBouncerEvents() }
+            launch { listenForFaceMessages() }
+            launch { listenForFingerprintMessages() }
+        }
+    }
+
     /** Initializes the bouncer message to default whenever it is shown. */
     fun onShown() {
         showDefaultMessage()
@@ -108,173 +127,161 @@
 
     private var lockoutCountdownJob: Job? = null
 
-    private fun defaultBouncerMessageInitializer() {
-        applicationScope.launch {
-            resetToDefault.emit(Unit)
-            authenticationInteractor.authenticationMethod
-                .flatMapLatest { authMethod ->
-                    if (authMethod == AuthenticationMethodModel.Sim) {
-                        resetToDefault.map {
-                            MessageViewModel(simBouncerInteractor.getDefaultMessage())
-                        }
-                    } else if (authMethod.isSecure) {
-                        combine(
-                            deviceUnlockedInteractor.deviceEntryRestrictionReason,
-                            lockoutMessage,
-                            deviceEntryBiometricsAllowedInteractor
-                                .isFingerprintCurrentlyAllowedOnBouncer,
-                            resetToDefault,
-                        ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
-                            lockoutMsg
-                                ?: deviceEntryRestrictedReason.toMessage(
-                                    authMethod,
-                                    isFpAllowedInBouncer
-                                )
-                        }
-                    } else {
-                        emptyFlow()
+    private suspend fun defaultBouncerMessageInitializer() {
+        resetToDefault.emit(Unit)
+        authenticationInteractor.authenticationMethod
+            .flatMapLatest { authMethod ->
+                if (authMethod == AuthenticationMethodModel.Sim) {
+                    resetToDefault.map {
+                        MessageViewModel(simBouncerInteractor.getDefaultMessage())
                     }
+                } else if (authMethod.isSecure) {
+                    combine(
+                        deviceUnlockedInteractor.deviceEntryRestrictionReason,
+                        lockoutMessage,
+                        deviceEntryBiometricsAllowedInteractor
+                            .isFingerprintCurrentlyAllowedOnBouncer,
+                        resetToDefault,
+                    ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
+                        lockoutMsg
+                            ?: deviceEntryRestrictedReason.toMessage(
+                                authMethod,
+                                isFpAllowedInBouncer
+                            )
+                    }
+                } else {
+                    emptyFlow()
                 }
-                .collectLatest { messageViewModel -> message.value = messageViewModel }
-        }
+            }
+            .collectLatest { messageViewModel -> message.value = messageViewModel }
     }
 
-    private fun listenForSimBouncerEvents() {
+    private suspend fun listenForSimBouncerEvents() {
         // Listen for any events from the SIM bouncer and update the message shown on the bouncer.
-        applicationScope.launch {
-            authenticationInteractor.authenticationMethod
-                .flatMapLatest { authMethod ->
-                    if (authMethod == AuthenticationMethodModel.Sim) {
-                        simBouncerInteractor.bouncerMessageChanged.map { simMsg ->
-                            simMsg?.let { MessageViewModel(it) }
-                        }
-                    } else {
-                        emptyFlow()
+        authenticationInteractor.authenticationMethod
+            .flatMapLatest { authMethod ->
+                if (authMethod == AuthenticationMethodModel.Sim) {
+                    simBouncerInteractor.bouncerMessageChanged.map { simMsg ->
+                        simMsg?.let { MessageViewModel(it) }
                     }
+                } else {
+                    emptyFlow()
                 }
-                .collectLatest {
-                    if (it != null) {
-                        message.value = it
-                    } else {
-                        resetToDefault.emit(Unit)
-                    }
+            }
+            .collectLatest {
+                if (it != null) {
+                    message.value = it
+                } else {
+                    resetToDefault.emit(Unit)
                 }
-        }
+            }
     }
 
-    private fun listenForFaceMessages() {
+    private suspend fun listenForFaceMessages() {
         // Listen for any events from face authentication and update the message shown on the
         // bouncer.
-        applicationScope.launch {
-            biometricMessageInteractor.faceMessage
-                .sample(
-                    authenticationInteractor.authenticationMethod,
-                    deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
-                )
-                .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
-                    val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
-                    val defaultPrimaryMessage =
-                        BouncerMessageStrings.defaultMessage(
-                                authMethod,
-                                fingerprintAllowedOnBouncer
+        biometricMessageInteractor.faceMessage
+            .sample(
+                authenticationInteractor.authenticationMethod,
+                deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
+            )
+            .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
+                val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
+                val defaultPrimaryMessage =
+                    BouncerMessageStrings.defaultMessage(authMethod, fingerprintAllowedOnBouncer)
+                        .primaryMessage
+                        .toResString()
+                message.value =
+                    when (faceMessage) {
+                        is FaceTimeoutMessage ->
+                            MessageViewModel(
+                                text = defaultPrimaryMessage,
+                                secondaryText = faceMessage.message,
+                                isUpdateAnimated = true
                             )
-                            .primaryMessage
-                            .toResString()
-                    message.value =
-                        when (faceMessage) {
-                            is FaceTimeoutMessage ->
-                                MessageViewModel(
-                                    text = defaultPrimaryMessage,
-                                    secondaryText = faceMessage.message,
-                                    isUpdateAnimated = true
-                                )
-                            is FaceLockoutMessage ->
-                                if (isFaceAuthStrong)
-                                    BouncerMessageStrings.class3AuthLockedOut(authMethod)
-                                        .toMessage()
-                                else
-                                    BouncerMessageStrings.faceLockedOut(
-                                            authMethod,
-                                            fingerprintAllowedOnBouncer
-                                        )
-                                        .toMessage()
-                            is FaceFailureMessage ->
-                                BouncerMessageStrings.incorrectFaceInput(
+                        is FaceLockoutMessage ->
+                            if (isFaceAuthStrong)
+                                BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
+                            else
+                                BouncerMessageStrings.faceLockedOut(
                                         authMethod,
                                         fingerprintAllowedOnBouncer
                                     )
                                     .toMessage()
-                            else ->
-                                MessageViewModel(
-                                    text = defaultPrimaryMessage,
-                                    secondaryText = faceMessage.message,
-                                    isUpdateAnimated = false
+                        is FaceFailureMessage ->
+                            BouncerMessageStrings.incorrectFaceInput(
+                                    authMethod,
+                                    fingerprintAllowedOnBouncer
                                 )
-                        }
-                    delay(MESSAGE_DURATION)
-                    resetToDefault.emit(Unit)
-                }
-        }
-    }
-
-    private fun listenForFingerprintMessages() {
-        applicationScope.launch {
-            // Listen for any events from fingerprint authentication and update the message shown
-            // on the bouncer.
-            biometricMessageInteractor.fingerprintMessage
-                .sample(
-                    authenticationInteractor.authenticationMethod,
-                    deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
-                )
-                .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
-                    val defaultPrimaryMessage =
-                        BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed)
-                            .primaryMessage
-                            .toResString()
-                    message.value =
-                        when (fingerprintMessage) {
-                            is FingerprintLockoutMessage ->
-                                BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
-                            is FingerprintFailureMessage ->
-                                BouncerMessageStrings.incorrectFingerprintInput(authMethod)
-                                    .toMessage()
-                            else ->
-                                MessageViewModel(
-                                    text = defaultPrimaryMessage,
-                                    secondaryText = fingerprintMessage.message,
-                                    isUpdateAnimated = false
-                                )
-                        }
-                    delay(MESSAGE_DURATION)
-                    resetToDefault.emit(Unit)
-                }
-        }
-    }
-
-    private fun listenForBouncerEvents() {
-        // Keeps the lockout message up-to-date.
-        applicationScope.launch {
-            bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() }
-        }
-
-        // Listens to relevant bouncer events
-        applicationScope.launch {
-            bouncerInteractor.onIncorrectBouncerInput
-                .sample(
-                    authenticationInteractor.authenticationMethod,
-                    deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
-                )
-                .collectLatest { (_, authMethod, isFingerprintAllowed) ->
-                    message.emit(
-                        BouncerMessageStrings.incorrectSecurityInput(
-                                authMethod,
-                                isFingerprintAllowed
+                                .toMessage()
+                        else ->
+                            MessageViewModel(
+                                text = defaultPrimaryMessage,
+                                secondaryText = faceMessage.message,
+                                isUpdateAnimated = false
                             )
-                            .toMessage()
+                    }
+                delay(MESSAGE_DURATION)
+                resetToDefault.emit(Unit)
+            }
+    }
+
+    private suspend fun listenForFingerprintMessages() {
+        // Listen for any events from fingerprint authentication and update the message shown
+        // on the bouncer.
+        biometricMessageInteractor.fingerprintMessage
+            .sample(
+                authenticationInteractor.authenticationMethod,
+                deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
+            )
+            .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
+                val defaultPrimaryMessage =
+                    BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed)
+                        .primaryMessage
+                        .toResString()
+                message.value =
+                    when (fingerprintMessage) {
+                        is FingerprintLockoutMessage ->
+                            BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
+                        is FingerprintFailureMessage ->
+                            BouncerMessageStrings.incorrectFingerprintInput(authMethod).toMessage()
+                        else ->
+                            MessageViewModel(
+                                text = defaultPrimaryMessage,
+                                secondaryText = fingerprintMessage.message,
+                                isUpdateAnimated = false
+                            )
+                    }
+                delay(MESSAGE_DURATION)
+                resetToDefault.emit(Unit)
+            }
+    }
+
+    private suspend fun listenForBouncerEvents() {
+        coroutineScope {
+            // Keeps the lockout message up-to-date.
+            launch { bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() } }
+
+            // Listens to relevant bouncer events
+            launch {
+                bouncerInteractor.onIncorrectBouncerInput
+                    .sample(
+                        authenticationInteractor.authenticationMethod,
+                        deviceEntryBiometricsAllowedInteractor
+                            .isFingerprintCurrentlyAllowedOnBouncer
                     )
-                    delay(MESSAGE_DURATION)
-                    resetToDefault.emit(Unit)
-                }
+                    .collectLatest { (_, authMethod, isFingerprintAllowed) ->
+                        message.emit(
+                            BouncerMessageStrings.incorrectSecurityInput(
+                                    authMethod,
+                                    isFingerprintAllowed
+                                )
+                                .toMessage()
+                        )
+                        delay(MESSAGE_DURATION)
+                        resetToDefault.emit(Unit)
+                    }
+            }
         }
     }
 
@@ -323,10 +330,10 @@
     }
 
     /** Shows the countdown message and refreshes it every second. */
-    private fun startLockoutCountdown() {
+    private suspend fun startLockoutCountdown() {
         lockoutCountdownJob?.cancel()
-        lockoutCountdownJob =
-            applicationScope.launch {
+        lockoutCountdownJob = coroutineScope {
+            launch {
                 authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
                     do {
                         val remainingSeconds = remainingLockoutSeconds()
@@ -352,6 +359,7 @@
                     lockoutCountdownJob = null
                 }
             }
+        }
     }
 
     private fun remainingLockoutSeconds(): Int {
@@ -365,20 +373,9 @@
 
     private fun Int.toResString(): String = applicationContext.getString(this)
 
-    init {
-        if (flags.isComposeBouncerOrSceneContainerEnabled()) {
-            applicationScope.launch {
-                // Update the lockout countdown whenever the selected user is switched.
-                selectedUser.collect { startLockoutCountdown() }
-            }
-
-            defaultBouncerMessageInitializer()
-
-            listenForSimBouncerEvents()
-            listenForBouncerEvents()
-            listenForFaceMessages()
-            listenForFingerprintMessages()
-        }
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerMessageViewModel
     }
 
     companion object {
@@ -398,40 +395,3 @@
      */
     val isUpdateAnimated: Boolean = true,
 )
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@Module
-object BouncerMessageViewModelModule {
-
-    @Provides
-    @SysUISingleton
-    fun viewModel(
-        @Application applicationContext: Context,
-        @Application applicationScope: CoroutineScope,
-        bouncerInteractor: BouncerInteractor,
-        simBouncerInteractor: SimBouncerInteractor,
-        authenticationInteractor: AuthenticationInteractor,
-        clock: SystemClock,
-        biometricMessageInteractor: BiometricMessageInteractor,
-        faceAuthInteractor: DeviceEntryFaceAuthInteractor,
-        deviceUnlockedInteractor: DeviceUnlockedInteractor,
-        deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
-        flags: ComposeBouncerFlags,
-        userSwitcherViewModel: UserSwitcherViewModel,
-    ): BouncerMessageViewModel {
-        return BouncerMessageViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            clock = clock,
-            biometricMessageInteractor = biometricMessageInteractor,
-            faceAuthInteractor = faceAuthInteractor,
-            deviceUnlockedInteractor = deviceUnlockedInteractor,
-            deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
-            flags = flags,
-            selectedUser = userSwitcherViewModel.selectedUser,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
new file mode 100644
index 0000000..2a27271
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.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.bouncer.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI state for user actions that can lead to navigation to other scenes when showing the
+ * bouncer scene.
+ */
+class BouncerSceneActionsViewModel
+@AssistedInject
+constructor(
+    private val bouncerInteractor: BouncerInteractor,
+) : SceneActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        bouncerInteractor.dismissDestination
+            .map { prevScene ->
+                mapOf(
+                    Back to UserActionResult(prevScene),
+                    Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
+                )
+            }
+            .collectLatest { actions -> setActions(actions) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerSceneActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
new file mode 100644
index 0000000..aede63b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -0,0 +1,362 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources
+import android.content.Context
+import android.graphics.Bitmap
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.type
+import androidx.core.graphics.drawable.toBitmap
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** Models UI state for the content of the bouncer scene. */
+class BouncerSceneContentViewModel
+@AssistedInject
+constructor(
+    @Application private val applicationContext: Context,
+    private val bouncerInteractor: BouncerInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory,
+    private val flags: ComposeBouncerFlags,
+    private val userSwitcher: UserSwitcherViewModel,
+    private val actionButtonInteractor: BouncerActionButtonInteractor,
+    private val pinViewModelFactory: PinBouncerViewModel.Factory,
+    private val patternViewModelFactory: PatternBouncerViewModel.Factory,
+    private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+) : SysUiViewModel() {
+    private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
+    val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
+
+    val message: BouncerMessageViewModel by lazy { bouncerMessageViewModelFactory.create() }
+
+    private val _userSwitcherDropdown =
+        MutableStateFlow<List<UserSwitcherDropdownItemViewModel>>(emptyList())
+    val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
+        _userSwitcherDropdown.asStateFlow()
+
+    val isUserSwitcherVisible: Boolean
+        get() = bouncerInteractor.isUserSwitcherVisible
+
+    /** View-model for the current UI, based on the current authentication method. */
+    private val _authMethodViewModel = MutableStateFlow<AuthMethodBouncerViewModel?>(null)
+    val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
+        _authMethodViewModel.asStateFlow()
+
+    /**
+     * A message for a dialog to show when the user has attempted the wrong credential too many
+     * times and now must wait a while before attempting again.
+     *
+     * If `null`, the lockout dialog should not be shown.
+     */
+    private val lockoutDialogMessage = MutableStateFlow<String?>(null)
+
+    /**
+     * A message for a dialog to show when the user has attempted the wrong credential too many
+     * times and their user/profile/device data is at risk of being wiped due to a Device Manager
+     * policy.
+     *
+     * If `null`, the wipe dialog should not be shown.
+     */
+    private val wipeDialogMessage = MutableStateFlow<String?>(null)
+
+    private val _dialogViewModel = MutableStateFlow<DialogViewModel?>(createDialogViewModel())
+    /**
+     * Models the dialog to be shown to the user, or `null` if no dialog should be shown.
+     *
+     * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user
+     * dismisses this dialog.
+     */
+    val dialogViewModel: StateFlow<DialogViewModel?> = _dialogViewModel.asStateFlow()
+
+    private val _actionButton = MutableStateFlow<BouncerActionButtonModel?>(null)
+    /**
+     * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
+     * be shown.
+     */
+    val actionButton: StateFlow<BouncerActionButtonModel?> = _actionButton.asStateFlow()
+
+    private val _isSideBySideSupported =
+        MutableStateFlow(isSideBySideSupported(authMethodViewModel.value))
+    /**
+     * Whether the "side-by-side" layout is supported.
+     *
+     * When presented on its own, without a user switcher (e.g. not on communal devices like
+     * tablets, for example), some authentication method UIs don't do well if they're shown in the
+     * side-by-side layout; these need to be shown with the standard layout so they can take up as
+     * much width as possible.
+     */
+    val isSideBySideSupported: StateFlow<Boolean> = _isSideBySideSupported.asStateFlow()
+
+    private val _isFoldSplitRequired =
+        MutableStateFlow(isFoldSplitRequired(authMethodViewModel.value))
+    /**
+     * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
+     * is required.
+     */
+    val isFoldSplitRequired: StateFlow<Boolean> = _isFoldSplitRequired.asStateFlow()
+
+    private val _isInputEnabled =
+        MutableStateFlow(authenticationInteractor.lockoutEndTimestamp == null)
+    private val isInputEnabled: StateFlow<Boolean> = _isInputEnabled.asStateFlow()
+
+    override suspend fun onActivated() {
+        coroutineScope {
+            launch { message.activate() }
+            launch {
+                authenticationInteractor.authenticationMethod
+                    .map(::getChildViewModel)
+                    .collectLatest { childViewModelOrNull ->
+                        _authMethodViewModel.value = childViewModelOrNull
+                        childViewModelOrNull?.activate()
+                    }
+            }
+
+            launch {
+                authenticationInteractor.upcomingWipe.collect { wipeModel ->
+                    wipeDialogMessage.value = wipeModel?.message
+                }
+            }
+
+            launch {
+                userSwitcher.selectedUser
+                    .map { it.image.toBitmap() }
+                    .collectLatest { _selectedUserImage.value = it }
+            }
+
+            launch {
+                combine(
+                        userSwitcher.users,
+                        userSwitcher.menu,
+                    ) { users, actions ->
+                        users.map { user ->
+                            UserSwitcherDropdownItemViewModel(
+                                icon = Icon.Loaded(user.image, contentDescription = null),
+                                text = user.name,
+                                onClick = user.onClicked ?: {},
+                            )
+                        } +
+                            actions.map { action ->
+                                UserSwitcherDropdownItemViewModel(
+                                    icon =
+                                        Icon.Resource(
+                                            action.iconResourceId,
+                                            contentDescription = null
+                                        ),
+                                    text = Text.Resource(action.textResourceId),
+                                    onClick = action.onClicked,
+                                )
+                            }
+                    }
+                    .collectLatest { _userSwitcherDropdown.value = it }
+            }
+
+            launch {
+                combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
+                    .collectLatest { _dialogViewModel.value = it }
+            }
+
+            launch {
+                actionButtonInteractor.actionButton.collectLatest { _actionButton.value = it }
+            }
+
+            launch {
+                authMethodViewModel
+                    .map { authMethod -> isSideBySideSupported(authMethod) }
+                    .collectLatest { _isSideBySideSupported.value = it }
+            }
+
+            launch {
+                authMethodViewModel
+                    .map { authMethod -> isFoldSplitRequired(authMethod) }
+                    .collectLatest { _isFoldSplitRequired.value = it }
+            }
+
+            launch {
+                message.isLockoutMessagePresent
+                    .map { lockoutMessagePresent -> !lockoutMessagePresent }
+                    .collectLatest { _isInputEnabled.value = it }
+            }
+        }
+    }
+
+    private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
+        return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
+    }
+
+    private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
+        return authMethod !is PasswordBouncerViewModel
+    }
+
+    private fun getChildViewModel(
+        authenticationMethod: AuthenticationMethodModel,
+    ): AuthMethodBouncerViewModel? {
+        // If the current child view-model matches the authentication method, reuse it instead of
+        // creating a new instance.
+        val childViewModel = authMethodViewModel.value
+        if (authenticationMethod == childViewModel?.authenticationMethod) {
+            return childViewModel
+        }
+
+        return when (authenticationMethod) {
+            is AuthenticationMethodModel.Pin ->
+                pinViewModelFactory.create(
+                    authenticationMethod = authenticationMethod,
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Sim ->
+                pinViewModelFactory.create(
+                    authenticationMethod = authenticationMethod,
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Password ->
+                passwordViewModelFactory.create(
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Pattern ->
+                patternViewModelFactory.create(
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            else -> null
+        }
+    }
+
+    private fun onIntentionalUserInput() {
+        message.showDefaultMessage()
+        bouncerInteractor.onIntentionalUserInput()
+    }
+
+    /**
+     * @return A message warning the user that the user/profile/device will be wiped upon a further
+     *   [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts.
+     */
+    private fun AuthenticationWipeModel.getAlmostAtWipeMessage(): String {
+        val message =
+            applicationContext.getString(
+                wipeTarget.messageIdForAlmostWipe,
+                failedAttempts,
+                remainingAttempts,
+            )
+        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
+            devicePolicyManager.resources.getString(
+                DevicePolicyResources.Strings.SystemUi
+                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE,
+                { message },
+                failedAttempts,
+                remainingAttempts,
+            ) ?: message
+        } else {
+            message
+        }
+    }
+
+    /**
+     * @return A message informing the user that their user/profile/device will be wiped promptly.
+     */
+    private fun AuthenticationWipeModel.getWipeMessage(): String {
+        val message = applicationContext.getString(wipeTarget.messageIdForWipe, failedAttempts)
+        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
+            devicePolicyManager.resources.getString(
+                DevicePolicyResources.Strings.SystemUi
+                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
+                { message },
+                failedAttempts,
+            ) ?: message
+        } else {
+            message
+        }
+    }
+
+    private val AuthenticationWipeModel.message: String
+        get() = if (remainingAttempts > 0) getAlmostAtWipeMessage() else getWipeMessage()
+
+    private fun createDialogViewModel(): DialogViewModel? {
+        val wipeText = wipeDialogMessage.value
+        val lockoutText = lockoutDialogMessage.value
+        return when {
+            // The wipe dialog takes priority over the lockout dialog.
+            wipeText != null ->
+                DialogViewModel(
+                    text = wipeText,
+                    onDismiss = { wipeDialogMessage.value = null },
+                )
+            lockoutText != null ->
+                DialogViewModel(
+                    text = lockoutText,
+                    onDismiss = { lockoutDialogMessage.value = null },
+                )
+            else -> null // No dialog to show.
+        }
+    }
+
+    /**
+     * Notifies that a key event has occurred.
+     *
+     * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
+     */
+    fun onKeyEvent(keyEvent: KeyEvent): Boolean {
+        return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
+            keyEvent.type,
+            keyEvent.nativeKeyEvent.keyCode
+        ) ?: false
+    }
+
+    data class DialogViewModel(
+        val text: String,
+
+        /** Callback to run after the dialog has been dismissed by the user. */
+        val onDismiss: () -> Unit,
+    )
+
+    data class UserSwitcherDropdownItemViewModel(
+        val icon: Icon,
+        val text: Text,
+        val onClick: () -> Unit,
+    )
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerSceneContentViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
deleted file mode 100644
index e2089bb..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.bouncer.ui.viewmodel
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyResources
-import android.content.Context
-import android.graphics.Bitmap
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.type
-import androidx.core.graphics.drawable.toBitmap
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
-import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.job
-import kotlinx.coroutines.launch
-
-/** Holds UI state and handles user input on bouncer UIs. */
-class BouncerViewModel(
-    @Application private val applicationContext: Context,
-    @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
-    @Application
-    private val applicationScope: CoroutineScope,
-    @Main private val mainDispatcher: CoroutineDispatcher,
-    private val bouncerInteractor: BouncerInteractor,
-    private val inputMethodInteractor: InputMethodInteractor,
-    private val simBouncerInteractor: SimBouncerInteractor,
-    private val authenticationInteractor: AuthenticationInteractor,
-    private val selectedUserInteractor: SelectedUserInteractor,
-    private val devicePolicyManager: DevicePolicyManager,
-    bouncerMessageViewModel: BouncerMessageViewModel,
-    flags: ComposeBouncerFlags,
-    selectedUser: Flow<UserViewModel>,
-    users: Flow<List<UserViewModel>>,
-    userSwitcherMenu: Flow<List<UserActionViewModel>>,
-    actionButton: Flow<BouncerActionButtonModel?>,
-) {
-    val selectedUserImage: StateFlow<Bitmap?> =
-        selectedUser
-            .map { it.image.toBitmap() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
-
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        bouncerInteractor.dismissDestination.map { prevScene ->
-            mapOf(
-                Back to UserActionResult(prevScene),
-                Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
-            )
-        }
-
-    val message: BouncerMessageViewModel = bouncerMessageViewModel
-
-    val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
-        combine(
-                users,
-                userSwitcherMenu,
-            ) { users, actions ->
-                users.map { user ->
-                    UserSwitcherDropdownItemViewModel(
-                        icon = Icon.Loaded(user.image, contentDescription = null),
-                        text = user.name,
-                        onClick = user.onClicked ?: {},
-                    )
-                } +
-                    actions.map { action ->
-                        UserSwitcherDropdownItemViewModel(
-                            icon = Icon.Resource(action.iconResourceId, contentDescription = null),
-                            text = Text.Resource(action.textResourceId),
-                            onClick = action.onClicked,
-                        )
-                    }
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = emptyList(),
-            )
-
-    val isUserSwitcherVisible: Boolean
-        get() = bouncerInteractor.isUserSwitcherVisible
-
-    // Handle to the scope of the child ViewModel (stored in [authMethod]).
-    private var childViewModelScope: CoroutineScope? = null
-
-    /** View-model for the current UI, based on the current authentication method. */
-    val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
-        authenticationInteractor.authenticationMethod
-            .map(::getChildViewModel)
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
-
-    /**
-     * A message for a dialog to show when the user has attempted the wrong credential too many
-     * times and now must wait a while before attempting again.
-     *
-     * If `null`, the lockout dialog should not be shown.
-     */
-    private val lockoutDialogMessage = MutableStateFlow<String?>(null)
-
-    /**
-     * A message for a dialog to show when the user has attempted the wrong credential too many
-     * times and their user/profile/device data is at risk of being wiped due to a Device Manager
-     * policy.
-     *
-     * If `null`, the wipe dialog should not be shown.
-     */
-    private val wipeDialogMessage = MutableStateFlow<String?>(null)
-
-    /**
-     * Models the dialog to be shown to the user, or `null` if no dialog should be shown.
-     *
-     * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user
-     * dismisses this dialog.
-     */
-    val dialogViewModel: StateFlow<DialogViewModel?> =
-        combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = createDialogViewModel(),
-            )
-
-    /**
-     * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
-     * be shown.
-     */
-    val actionButton: StateFlow<BouncerActionButtonModel?> =
-        actionButton.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    /**
-     * Whether the "side-by-side" layout is supported.
-     *
-     * When presented on its own, without a user switcher (e.g. not on communal devices like
-     * tablets, for example), some authentication method UIs don't do well if they're shown in the
-     * side-by-side layout; these need to be shown with the standard layout so they can take up as
-     * much width as possible.
-     */
-    val isSideBySideSupported: StateFlow<Boolean> =
-        authMethodViewModel
-            .map { authMethod -> isSideBySideSupported(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = isSideBySideSupported(authMethodViewModel.value),
-            )
-
-    /**
-     * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
-     * is required.
-     */
-    val isFoldSplitRequired: StateFlow<Boolean> =
-        authMethodViewModel
-            .map { authMethod -> isFoldSplitRequired(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = isFoldSplitRequired(authMethodViewModel.value),
-            )
-
-    private val isInputEnabled: StateFlow<Boolean> =
-        bouncerMessageViewModel.isLockoutMessagePresent
-            .map { lockoutMessagePresent -> !lockoutMessagePresent }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = authenticationInteractor.lockoutEndTimestamp == null,
-            )
-
-    init {
-        if (flags.isComposeBouncerOrSceneContainerEnabled()) {
-            // Keeps the upcoming wipe dialog up-to-date.
-            applicationScope.launch {
-                authenticationInteractor.upcomingWipe.collect { wipeModel ->
-                    wipeDialogMessage.value = wipeModel?.message
-                }
-            }
-        }
-    }
-
-    private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
-        return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
-    }
-
-    private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
-        return authMethod !is PasswordBouncerViewModel
-    }
-
-    private fun getChildViewModel(
-        authenticationMethod: AuthenticationMethodModel,
-    ): AuthMethodBouncerViewModel? {
-        // If the current child view-model matches the authentication method, reuse it instead of
-        // creating a new instance.
-        val childViewModel = authMethodViewModel.value
-        if (authenticationMethod == childViewModel?.authenticationMethod) {
-            return childViewModel
-        }
-
-        childViewModelScope?.cancel()
-        val newViewModelScope = createChildCoroutineScope(applicationScope)
-        childViewModelScope = newViewModelScope
-        return when (authenticationMethod) {
-            is AuthenticationMethodModel.Pin ->
-                PinBouncerViewModel(
-                    applicationContext = applicationContext,
-                    viewModelScope = newViewModelScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = isInputEnabled,
-                    simBouncerInteractor = simBouncerInteractor,
-                    authenticationMethod = authenticationMethod,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            is AuthenticationMethodModel.Sim ->
-                PinBouncerViewModel(
-                    applicationContext = applicationContext,
-                    viewModelScope = newViewModelScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = isInputEnabled,
-                    simBouncerInteractor = simBouncerInteractor,
-                    authenticationMethod = authenticationMethod,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            is AuthenticationMethodModel.Password ->
-                PasswordBouncerViewModel(
-                    viewModelScope = newViewModelScope,
-                    isInputEnabled = isInputEnabled,
-                    interactor = bouncerInteractor,
-                    inputMethodInteractor = inputMethodInteractor,
-                    selectedUserInteractor = selectedUserInteractor,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            is AuthenticationMethodModel.Pattern ->
-                PatternBouncerViewModel(
-                    applicationContext = applicationContext,
-                    viewModelScope = newViewModelScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = isInputEnabled,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            else -> null
-        }
-    }
-
-    private fun onIntentionalUserInput() {
-        message.showDefaultMessage()
-        bouncerInteractor.onIntentionalUserInput()
-    }
-
-    private fun createChildCoroutineScope(parentScope: CoroutineScope): CoroutineScope {
-        return CoroutineScope(
-            SupervisorJob(parent = parentScope.coroutineContext.job) + mainDispatcher
-        )
-    }
-
-    /**
-     * @return A message warning the user that the user/profile/device will be wiped upon a further
-     *   [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts.
-     */
-    private fun AuthenticationWipeModel.getAlmostAtWipeMessage(): String {
-        val message =
-            applicationContext.getString(
-                wipeTarget.messageIdForAlmostWipe,
-                failedAttempts,
-                remainingAttempts,
-            )
-        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
-            devicePolicyManager.resources.getString(
-                DevicePolicyResources.Strings.SystemUi
-                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE,
-                { message },
-                failedAttempts,
-                remainingAttempts,
-            ) ?: message
-        } else {
-            message
-        }
-    }
-
-    /**
-     * @return A message informing the user that their user/profile/device will be wiped promptly.
-     */
-    private fun AuthenticationWipeModel.getWipeMessage(): String {
-        val message = applicationContext.getString(wipeTarget.messageIdForWipe, failedAttempts)
-        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
-            devicePolicyManager.resources.getString(
-                DevicePolicyResources.Strings.SystemUi
-                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
-                { message },
-                failedAttempts,
-            ) ?: message
-        } else {
-            message
-        }
-    }
-
-    private val AuthenticationWipeModel.message: String
-        get() = if (remainingAttempts > 0) getAlmostAtWipeMessage() else getWipeMessage()
-
-    private fun createDialogViewModel(): DialogViewModel? {
-        val wipeText = wipeDialogMessage.value
-        val lockoutText = lockoutDialogMessage.value
-        return when {
-            // The wipe dialog takes priority over the lockout dialog.
-            wipeText != null ->
-                DialogViewModel(
-                    text = wipeText,
-                    onDismiss = { wipeDialogMessage.value = null },
-                )
-            lockoutText != null ->
-                DialogViewModel(
-                    text = lockoutText,
-                    onDismiss = { lockoutDialogMessage.value = null },
-                )
-            else -> null // No dialog to show.
-        }
-    }
-
-    /**
-     * Notifies that a key event has occurred.
-     *
-     * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
-     */
-    fun onKeyEvent(keyEvent: KeyEvent): Boolean {
-        return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
-            keyEvent.type,
-            keyEvent.nativeKeyEvent.keyCode
-        ) ?: false
-    }
-
-    data class DialogViewModel(
-        val text: String,
-
-        /** Callback to run after the dialog has been dismissed by the user. */
-        val onDismiss: () -> Unit,
-    )
-
-    data class UserSwitcherDropdownItemViewModel(
-        val icon: Icon,
-        val text: Text,
-        val onClick: () -> Unit,
-    )
-}
-
-@Module
-object BouncerViewModelModule {
-
-    @Provides
-    @SysUISingleton
-    fun viewModel(
-        @Application applicationContext: Context,
-        @Application applicationScope: CoroutineScope,
-        @Main mainDispatcher: CoroutineDispatcher,
-        bouncerInteractor: BouncerInteractor,
-        imeInteractor: InputMethodInteractor,
-        simBouncerInteractor: SimBouncerInteractor,
-        actionButtonInteractor: BouncerActionButtonInteractor,
-        authenticationInteractor: AuthenticationInteractor,
-        selectedUserInteractor: SelectedUserInteractor,
-        flags: ComposeBouncerFlags,
-        userSwitcherViewModel: UserSwitcherViewModel,
-        devicePolicyManager: DevicePolicyManager,
-        bouncerMessageViewModel: BouncerMessageViewModel,
-    ): BouncerViewModel {
-        return BouncerViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            mainDispatcher = mainDispatcher,
-            bouncerInteractor = bouncerInteractor,
-            inputMethodInteractor = imeInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            selectedUserInteractor = selectedUserInteractor,
-            devicePolicyManager = devicePolicyManager,
-            bouncerMessageViewModel = bouncerMessageViewModel,
-            flags = flags,
-            selectedUser = userSwitcherViewModel.selectedUser,
-            users = userSwitcherViewModel.users,
-            userSwitcherMenu = userSwitcherViewModel.menu,
-            actionButton = actionButtonInteractor.actionButton,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 052fb6b..9ead7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -23,29 +23,33 @@
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.onSubscriberAdded
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
-class PasswordBouncerViewModel(
-    viewModelScope: CoroutineScope,
-    isInputEnabled: StateFlow<Boolean>,
+class PasswordBouncerViewModel
+@AssistedInject
+constructor(
     interactor: BouncerInteractor,
-    private val onIntentionalUserInput: () -> Unit,
     private val inputMethodInteractor: InputMethodInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
+    @Assisted isInputEnabled: StateFlow<Boolean>,
+    @Assisted private val onIntentionalUserInput: () -> Unit,
 ) :
     AuthMethodBouncerViewModel(
-        viewModelScope = viewModelScope,
         interactor = interactor,
         isInputEnabled = isInputEnabled,
     ) {
@@ -59,28 +63,70 @@
 
     override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
 
+    private val _isImeSwitcherButtonVisible = MutableStateFlow(false)
     /** Informs the UI whether the input method switcher button should be visible. */
-    val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow()
+    val isImeSwitcherButtonVisible: StateFlow<Boolean> = _isImeSwitcherButtonVisible.asStateFlow()
 
     /** Whether the text field element currently has focus. */
     private val isTextFieldFocused = MutableStateFlow(false)
 
+    private val _isTextFieldFocusRequested =
+        MutableStateFlow(isInputEnabled.value && !isTextFieldFocused.value)
     /** Whether the UI should request focus on the text field element. */
-    val isTextFieldFocusRequested =
-        combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = isInputEnabled.value && !isTextFieldFocused.value,
-            )
+    val isTextFieldFocusRequested = _isTextFieldFocusRequested.asStateFlow()
 
+    private val _selectedUserId = MutableStateFlow(selectedUserInteractor.getSelectedUserId())
     /** The ID of the currently-selected user. */
-    val selectedUserId: StateFlow<Int> =
-        selectedUserInteractor.selectedUser.stateIn(
-            scope = viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = selectedUserInteractor.getSelectedUserId(),
-        )
+    val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow()
+
+    private val requests = Channel<Request>(Channel.BUFFERED)
+
+    override suspend fun onActivated() {
+        coroutineScope {
+            launch { super.onActivated() }
+            launch {
+                requests.receiveAsFlow().collect { request ->
+                    when (request) {
+                        is OnImeSwitcherButtonClicked -> {
+                            inputMethodInteractor.showInputMethodPicker(
+                                displayId = request.displayId,
+                                showAuxiliarySubtypes = false,
+                            )
+                        }
+                        is OnImeDismissed -> {
+                            interactor.onImeHiddenByUser()
+                        }
+                    }
+                }
+            }
+            launch {
+                combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
+                        hasInput && !hasFocus
+                    }
+                    .collectLatest { _isTextFieldFocusRequested.value = it }
+            }
+            launch {
+                selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it }
+            }
+            launch {
+                // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
+                // whenever
+                // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+                combine(
+                        // InputMethodManagerService sometimes takes some time to update its
+                        // internal
+                        // state when the selected user changes. As a workaround, delay fetching the
+                        // IME
+                        // info.
+                        selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
+                        _isImeSwitcherButtonVisible.onSubscriberAdded()
+                    ) { selectedUserId, _ ->
+                        inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+                    }
+                    .collectLatest { _isImeSwitcherButtonVisible.value = it }
+            }
+        }
+    }
 
     override fun onHidden() {
         super.onHidden()
@@ -106,9 +152,7 @@
 
     /** Notifies that the user clicked the button to change the input method. */
     fun onImeSwitcherButtonClicked(displayId: Int) {
-        viewModelScope.launch {
-            inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
-        }
+        requests.trySend(OnImeSwitcherButtonClicked(displayId))
     }
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
@@ -120,7 +164,7 @@
 
     /** Notifies that the user has dismissed the software keyboard (IME). */
     fun onImeDismissed() {
-        viewModelScope.launch { interactor.onImeHiddenByUser() }
+        requests.trySend(OnImeDismissed)
     }
 
     /** Notifies that the password text field has gained or lost focus. */
@@ -128,34 +172,21 @@
         isTextFieldFocused.value = isFocused
     }
 
-    /**
-     * Whether the input method switcher button should be displayed in the password bouncer UI. The
-     * value may be stale at the moment of subscription to this flow, but it is guaranteed to be
-     * shortly updated with a fresh value.
-     *
-     * Note: Each added subscription triggers an IPC call in the background, so this should only be
-     * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown).
-     */
-    private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> {
-        val isImeSwitcherButtonVisible = MutableStateFlow(value = false)
-        viewModelScope.launch {
-            // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever
-            // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
-            combine(
-                    // InputMethodManagerService sometimes takes some time to update its internal
-                    // state when the selected user changes. As a workaround, delay fetching the IME
-                    // info.
-                    selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
-                    isImeSwitcherButtonVisible.onSubscriberAdded()
-                ) { selectedUserId, _ ->
-                    inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
-                }
-                .collect { isImeSwitcherButtonVisible.value = it }
-        }
-        return isImeSwitcherButtonVisible.asStateFlow()
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PasswordBouncerViewModel
     }
 
     companion object {
         @VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds
     }
+
+    private sealed interface Request
+
+    private data class OnImeSwitcherButtonClicked(val displayId: Int) : Request
+
+    private data object OnImeDismissed : Request
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 8b9c0a9a..b1df04b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -22,28 +22,32 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.pow
 import kotlin.math.sqrt
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
-class PatternBouncerViewModel(
+class PatternBouncerViewModel
+@AssistedInject
+constructor(
     private val applicationContext: Context,
-    viewModelScope: CoroutineScope,
     interactor: BouncerInteractor,
-    isInputEnabled: StateFlow<Boolean>,
-    private val onIntentionalUserInput: () -> Unit,
+    @Assisted isInputEnabled: StateFlow<Boolean>,
+    @Assisted private val onIntentionalUserInput: () -> Unit,
 ) :
     AuthMethodBouncerViewModel(
-        viewModelScope = viewModelScope,
         interactor = interactor,
         isInputEnabled = isInputEnabled,
     ) {
@@ -54,17 +58,10 @@
     /** The number of rows in the dot grid. */
     val rowCount = 3
 
-    private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
-
+    private val selectedDotSet = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
+    private val selectedDotList = MutableStateFlow(selectedDotSet.value.toList())
     /** The dots that were selected by the user, in the order of selection. */
-    val selectedDots: StateFlow<List<PatternDotViewModel>> =
-        _selectedDots
-            .map { it.toList() }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = emptyList(),
-            )
+    val selectedDots: StateFlow<List<PatternDotViewModel>> = selectedDotList.asStateFlow()
 
     private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null)
 
@@ -83,6 +80,17 @@
 
     override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
 
+    override suspend fun onActivated() {
+        coroutineScope {
+            launch { super.onActivated() }
+            launch {
+                selectedDotSet
+                    .map { it.toList() }
+                    .collectLatest { selectedDotList.value = it.toList() }
+            }
+        }
+    }
+
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
         onIntentionalUserInput()
@@ -120,7 +128,7 @@
         }
 
         val hitDot = dots.value.firstOrNull { dot -> dot.x == dotColumn && dot.y == dotRow }
-        if (hitDot != null && !_selectedDots.value.contains(hitDot)) {
+        if (hitDot != null && !selectedDotSet.value.contains(hitDot)) {
             val skippedOverDots =
                 currentDot.value?.let { previousDot ->
                     buildList {
@@ -147,9 +155,9 @@
                     }
                 } ?: emptyList()
 
-            _selectedDots.value =
+            selectedDotSet.value =
                 linkedSetOf<PatternDotViewModel>().apply {
-                    addAll(_selectedDots.value)
+                    addAll(selectedDotSet.value)
                     addAll(skippedOverDots)
                     add(hitDot)
                 }
@@ -172,11 +180,11 @@
     override fun clearInput() {
         _dots.value = defaultDots()
         _currentDot.value = null
-        _selectedDots.value = linkedSetOf()
+        selectedDotSet.value = linkedSetOf()
     }
 
     override fun getInput(): List<Any> {
-        return _selectedDots.value.map(PatternDotViewModel::toCoordinate)
+        return selectedDotSet.value.map(PatternDotViewModel::toCoordinate)
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
@@ -204,6 +212,14 @@
         max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
     }
 
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PatternBouncerViewModel
+    }
+
     companion object {
         private const val MIN_DOT_HIT_FACTOR = 0.2f
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index aa447ff..cb36560 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -32,29 +32,34 @@
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.res.R
-import kotlinx.coroutines.CoroutineScope
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
-class PinBouncerViewModel(
+class PinBouncerViewModel
+@AssistedInject
+constructor(
     applicationContext: Context,
-    viewModelScope: CoroutineScope,
     interactor: BouncerInteractor,
-    isInputEnabled: StateFlow<Boolean>,
-    private val onIntentionalUserInput: () -> Unit,
     private val simBouncerInteractor: SimBouncerInteractor,
-    authenticationMethod: AuthenticationMethodModel,
+    @Assisted isInputEnabled: StateFlow<Boolean>,
+    @Assisted private val onIntentionalUserInput: () -> Unit,
+    @Assisted override val authenticationMethod: AuthenticationMethodModel,
 ) :
     AuthMethodBouncerViewModel(
-        viewModelScope = viewModelScope,
         interactor = interactor,
         isInputEnabled = isInputEnabled,
     ) {
@@ -73,69 +78,89 @@
     /** Currently entered pin keys. */
     val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
 
+    private val _hintedPinLength = MutableStateFlow<Int?>(null)
     /** The length of the PIN for which we should show a hint. */
-    val hintedPinLength: StateFlow<Int?> =
-        if (isSimAreaVisible) {
-                flowOf(null)
-            } else {
-                interactor.hintedPinLength
-            }
-            .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+    val hintedPinLength: StateFlow<Int?> = _hintedPinLength.asStateFlow()
 
+    private val _backspaceButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden)
     /** Appearance of the backspace button. */
     val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
-        combine(
-                mutablePinInput,
-                interactor.isAutoConfirmEnabled,
-            ) { mutablePinEntries, isAutoConfirmEnabled ->
-                computeBackspaceButtonAppearance(
-                    pinInput = mutablePinEntries,
-                    isAutoConfirmEnabled = isAutoConfirmEnabled,
-                )
-            }
-            .stateIn(
-                scope = viewModelScope,
-                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
-                // downstream continues to receive old/stale/cached values.
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ActionButtonAppearance.Hidden,
-            )
+        _backspaceButtonAppearance.asStateFlow()
 
+    private val _confirmButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden)
     /** Appearance of the confirm button. */
     val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
-        interactor.isAutoConfirmEnabled
-            .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ActionButtonAppearance.Hidden,
-            )
-
-    override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
+        _confirmButtonAppearance.asStateFlow()
 
     override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
-    init {
-        viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+    private val requests = Channel<Request>(Channel.BUFFERED)
+
+    override suspend fun onActivated() {
+        coroutineScope {
+            launch { super.onActivated() }
+            launch {
+                requests.receiveAsFlow().collect { request ->
+                    when (request) {
+                        is OnErrorDialogDismissed -> {
+                            simBouncerInteractor.onErrorDialogDismissed()
+                        }
+                        is OnAuthenticateButtonClickedForSim -> {
+                            isSimUnlockingDialogVisible.value = true
+                            simBouncerInteractor.verifySim(getInput())
+                            isSimUnlockingDialogVisible.value = false
+                            clearInput()
+                        }
+                    }
+                }
+            }
+            launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+            launch {
+                if (isSimAreaVisible) {
+                        flowOf(null)
+                    } else {
+                        interactor.hintedPinLength
+                    }
+                    .collectLatest { _hintedPinLength.value = it }
+            }
+            launch {
+                combine(
+                        mutablePinInput,
+                        interactor.isAutoConfirmEnabled,
+                    ) { mutablePinEntries, isAutoConfirmEnabled ->
+                        computeBackspaceButtonAppearance(
+                            pinInput = mutablePinEntries,
+                            isAutoConfirmEnabled = isAutoConfirmEnabled,
+                        )
+                    }
+                    .collectLatest { _backspaceButtonAppearance.value = it }
+            }
+            launch {
+                interactor.isAutoConfirmEnabled
+                    .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
+                    .collectLatest { _confirmButtonAppearance.value = it }
+            }
+            launch {
+                interactor.isPinEnhancedPrivacyEnabled
+                    .map { !it }
+                    .collectLatest { _isDigitButtonAnimationEnabled.value = it }
+            }
+        }
     }
 
     /** Notifies that the user dismissed the sim pin error dialog. */
     fun onErrorDialogDismissed() {
-        viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() }
+        requests.trySend(OnErrorDialogDismissed)
     }
 
+    private val _isDigitButtonAnimationEnabled =
+        MutableStateFlow(!interactor.isPinEnhancedPrivacyEnabled.value)
     /**
      * Whether the digit buttons should be animated when touched. Note that this doesn't affect the
      * delete or enter buttons; those should always animate.
      */
     val isDigitButtonAnimationEnabled: StateFlow<Boolean> =
-        interactor.isPinEnhancedPrivacyEnabled
-            .map { !it }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = !interactor.isPinEnhancedPrivacyEnabled.value,
-            )
+        _isDigitButtonAnimationEnabled.asStateFlow()
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
@@ -163,19 +188,14 @@
     /** Notifies that the user clicked the "enter" button. */
     fun onAuthenticateButtonClicked() {
         if (authenticationMethod == AuthenticationMethodModel.Sim) {
-            viewModelScope.launch {
-                isSimUnlockingDialogVisible.value = true
-                simBouncerInteractor.verifySim(getInput())
-                isSimUnlockingDialogVisible.value = false
-                clearInput()
-            }
+            requests.trySend(OnAuthenticateButtonClickedForSim)
         } else {
             tryAuthenticate(useAutoConfirm = false)
         }
     }
 
     fun onDisableEsimButtonClicked() {
-        viewModelScope.launch { simBouncerInteractor.disableEsim() }
+        simBouncerInteractor.disableEsim()
     }
 
     /** Resets the sim screen and shows a default message. */
@@ -242,6 +262,21 @@
             else -> false
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+            authenticationMethod: AuthenticationMethodModel,
+        ): PinBouncerViewModel
+    }
+
+    private sealed interface Request
+
+    private data object OnErrorDialogDismissed : Request
+
+    private data object OnAuthenticateButtonClickedForSim : Request
 }
 
 /** Appearance of pin-pad action buttons. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
new file mode 100644
index 0000000..7453368
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.app.ActivityManager
+import com.android.systemui.common.usagestats.domain.UsageStatsInteractor
+import com.android.systemui.common.usagestats.shared.model.ActivityEventModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
+import com.android.systemui.util.kotlin.race
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Detects activity starts that occur while the communal hub is showing, within a short delay of a
+ * widget interaction occurring. Used for detecting non-activity trampolines which otherwise would
+ * not prompt the user for authentication.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class WidgetTrampolineInteractor
+@Inject
+constructor(
+    private val activityStarter: ActivityStarter,
+    private val systemClock: SystemClock,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val taskStackChangeListeners: TaskStackChangeListeners,
+    private val usageStatsInteractor: UsageStatsInteractor,
+    @CommunalLog logBuffer: LogBuffer,
+) {
+    private companion object {
+        const val TAG = "WidgetTrampolineInteractor"
+    }
+
+    private val logger = Logger(logBuffer, TAG)
+
+    /** Waits for a new task to be moved to the foreground. */
+    private suspend fun waitForNewForegroundTask() = suspendCancellableCoroutine { cont ->
+        val listener =
+            object : TaskStackChangeListener {
+                override fun onTaskMovedToFront(taskInfo: ActivityManager.RunningTaskInfo) {
+                    if (!cont.isCompleted) {
+                        cont.resume(Unit, null)
+                    }
+                }
+            }
+        taskStackChangeListeners.registerTaskStackListener(listener)
+        cont.invokeOnCancellation { taskStackChangeListeners.unregisterTaskStackListener(listener) }
+    }
+
+    /**
+     * Waits for an activity to enter a [ActivityEventModel.Lifecycle.RESUMED] state by periodically
+     * polling the system to see if any activities have started.
+     */
+    private suspend fun waitForActivityStartByPolling(startTime: Long): Boolean {
+        while (true) {
+            val events = usageStatsInteractor.queryActivityEvents(startTime = startTime)
+            if (events.any { event -> event.lifecycle == ActivityEventModel.Lifecycle.RESUMED }) {
+                return true
+            } else {
+                // Poll again in the future to check if an activity started.
+                delay(200.milliseconds)
+            }
+        }
+    }
+
+    /** Waits for a transition away from the hub to occur. */
+    private suspend fun waitForTransitionAwayFromHub() {
+        keyguardTransitionInteractor
+            .isFinishedIn(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
+            .takeWhile { it }
+            .collect {}
+    }
+
+    private suspend fun waitForActivityStartWhileOnHub(): Boolean {
+        val startTime = systemClock.currentTimeMillis()
+        return try {
+            return withTimeout(1.seconds) {
+                race(
+                    {
+                        waitForNewForegroundTask()
+                        true
+                    },
+                    { waitForActivityStartByPolling(startTime) },
+                    {
+                        waitForTransitionAwayFromHub()
+                        false
+                    },
+                )
+            }
+        } catch (e: TimeoutCancellationException) {
+            false
+        }
+    }
+
+    /**
+     * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it
+     * does. This can detect activities started due to broadcast trampolines from widgets.
+     */
+    suspend fun waitForActivityStartAndDismissKeyguard() {
+        if (waitForActivityStartWhileOnHub()) {
+            logger.d("Detected trampoline, requesting unlock")
+            activityStarter.dismissKeyguardThenExecute(
+                /* action= */ { false },
+                /* cancel= */ null,
+                /* afterKeyguardGone= */ false
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index c4edcac..99e3232 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -48,7 +48,17 @@
         InteractionHandlerDelegate(
             communalSceneInteractor,
             findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
-            intentStarter = this::startIntent,
+            intentStarter =
+                object : InteractionHandlerDelegate.IntentStarter {
+                    override fun startActivity(
+                        intent: PendingIntent,
+                        fillInIntent: Intent,
+                        activityOptions: ActivityOptions,
+                        controller: ActivityTransitionAnimator.Controller?
+                    ): Boolean {
+                        return startIntent(intent, fillInIntent, activityOptions, controller)
+                    }
+                },
             logger = Logger(logBuffer, TAG),
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
index 7a05671..5f421fd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
@@ -21,10 +21,13 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.core.view.doOnLayout
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.util.WidgetViewFactory
 import com.android.systemui.util.kotlin.DisposableHandles
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 
@@ -44,13 +47,8 @@
         val loadingJob =
             applicationScope.launch("$TAG#createWidgetView") {
                 val widget = factory.createWidget(context, model, size)
-                // TODO(b/358662507): Remove this workaround
-                (container.parent as? ViewGroup)?.let { parent ->
-                    val index = parent.indexOfChild(container)
-                    parent.removeView(container)
-                    parent.addView(container, index)
-                }
-                container.setView(widget)
+                waitForLayout(container)
+                container.post { container.setView(widget) }
             }
 
         disposables += DisposableHandle { loadingJob.cancel() }
@@ -58,6 +56,10 @@
 
         return disposables
     }
+
+    private suspend fun waitForLayout(container: FrameLayout) = suspendCoroutine { cont ->
+        container.doOnLayout { cont.resume(Unit) }
+    }
 }
 
 private fun ViewGroup.setView(view: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index d2029d5..5e21afa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -19,6 +19,7 @@
 import android.app.ActivityOptions
 import android.app.PendingIntent
 import android.content.Intent
+import android.util.Pair as UtilPair
 import android.view.View
 import android.widget.RemoteViews
 import androidx.core.util.component1
@@ -36,14 +37,28 @@
     private val logger: Logger,
 ) : RemoteViews.InteractionHandler {
 
-    /** Responsible for starting the pending intent for launching activities. */
-    fun interface IntentStarter {
-        fun startPendingIntent(
+    interface IntentStarter {
+        /** Responsible for starting the pending intent for launching activities. */
+        fun startActivity(
             intent: PendingIntent,
             fillInIntent: Intent,
             activityOptions: ActivityOptions,
             controller: ActivityTransitionAnimator.Controller?,
         ): Boolean
+
+        /** Responsible for starting the pending intent for non-activity launches. */
+        fun startPendingIntent(
+            view: View,
+            pendingIntent: PendingIntent,
+            fillInIntent: Intent,
+            activityOptions: ActivityOptions,
+        ): Boolean {
+            return RemoteViews.startPendingIntent(
+                view,
+                pendingIntent,
+                UtilPair(fillInIntent, activityOptions),
+            )
+        }
     }
 
     override fun onInteraction(
@@ -55,7 +70,7 @@
             str1 = pendingIntent.toLoggingString()
             str2 = pendingIntent.creatorPackage
         }
-        val launchOptions = response.getLaunchOptions(view)
+        val (fillInIntent, activityOptions) = response.getLaunchOptions(view)
         return when {
             pendingIntent.isActivity -> {
                 // Forward the fill-in intent and activity options retrieved from the response
@@ -67,15 +82,15 @@
                         communalSceneInteractor.setIsLaunchingWidget(true)
                         CommunalTransitionAnimatorController(it, communalSceneInteractor)
                     }
-                val (fillInIntent, activityOptions) = launchOptions
-                intentStarter.startPendingIntent(
+                intentStarter.startActivity(
                     pendingIntent,
                     fillInIntent,
                     activityOptions,
                     animationController
                 )
             }
-            else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
+            else ->
+                intentStarter.startPendingIntent(view, pendingIntent, fillInIntent, activityOptions)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 0eeb506..121b4a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -21,22 +21,30 @@
 import android.content.Intent
 import android.view.View
 import android.widget.RemoteViews
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.communalWidgetTrampolineFix
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.WidgetTrampolineInteractor
 import com.android.systemui.communal.util.InteractionHandlerDelegate
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 
 @SysUISingleton
 class WidgetInteractionHandler
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val activityStarter: ActivityStarter,
     communalSceneInteractor: CommunalSceneInteractor,
+    private val widgetTrampolineInteractor: WidgetTrampolineInteractor,
     @CommunalLog val logBuffer: LogBuffer,
 ) : RemoteViews.InteractionHandler {
 
@@ -48,7 +56,52 @@
         InteractionHandlerDelegate(
             communalSceneInteractor,
             findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
-            intentStarter = this::startIntent,
+            intentStarter =
+                object : InteractionHandlerDelegate.IntentStarter {
+                    private var job: Job? = null
+
+                    override fun startActivity(
+                        intent: PendingIntent,
+                        fillInIntent: Intent,
+                        activityOptions: ActivityOptions,
+                        controller: ActivityTransitionAnimator.Controller?
+                    ): Boolean {
+                        cancelTrampolineMonitoring()
+                        return startActivityIntent(
+                            intent,
+                            fillInIntent,
+                            activityOptions,
+                            controller
+                        )
+                    }
+
+                    override fun startPendingIntent(
+                        view: View,
+                        pendingIntent: PendingIntent,
+                        fillInIntent: Intent,
+                        activityOptions: ActivityOptions
+                    ): Boolean {
+                        cancelTrampolineMonitoring()
+                        if (communalWidgetTrampolineFix()) {
+                            job =
+                                applicationScope.launch("$TAG#monitorForActivityStart") {
+                                    widgetTrampolineInteractor
+                                        .waitForActivityStartAndDismissKeyguard()
+                                }
+                        }
+                        return super.startPendingIntent(
+                            view,
+                            pendingIntent,
+                            fillInIntent,
+                            activityOptions
+                        )
+                    }
+
+                    private fun cancelTrampolineMonitoring() {
+                        job?.cancel()
+                        job = null
+                    }
+                },
             logger = Logger(logBuffer, TAG),
         )
 
@@ -58,7 +111,7 @@
         response: RemoteViews.RemoteResponse
     ): Boolean = delegate.onInteraction(view, pendingIntent, response)
 
-    private fun startIntent(
+    private fun startActivityIntent(
         pendingIntent: PendingIntent,
         fillInIntent: Intent,
         extraOptions: ActivityOptions,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
index b9b3895..36b9ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.inputdevice.tutorial.data.repository
 
 import android.content.Context
+import androidx.annotation.VisibleForTesting
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.core.booleanPreferencesKey
@@ -33,15 +34,19 @@
 import kotlinx.coroutines.flow.map
 
 @SysUISingleton
-class TutorialSchedulerRepository
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    @Background private val backgroundScope: CoroutineScope
+class TutorialSchedulerRepository(
+    private val applicationContext: Context,
+    backgroundScope: CoroutineScope,
+    dataStoreName: String
 ) {
+    @Inject
+    constructor(
+        @Application applicationContext: Context,
+        @Background backgroundScope: CoroutineScope
+    ) : this(applicationContext, backgroundScope, dataStoreName = "TutorialScheduler")
 
     private val Context.dataStore: DataStore<Preferences> by
-        preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope)
+        preferencesDataStore(name = dataStoreName, scope = backgroundScope)
 
     suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched
 
@@ -81,8 +86,12 @@
     private fun getConnectKey(device: DeviceType) =
         longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
 
+    @VisibleForTesting
+    suspend fun clearDataStore() {
+        applicationContext.dataStore.edit { it.clear() }
+    }
+
     companion object {
-        const val DATASTORE_NAME = "TutorialScheduler"
         const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED"
         const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 81b0064..49303e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -91,11 +91,11 @@
      * the z-order (which is not really above the system UI window, but rather - the lock-screen
      * becomes invisible to reveal the "occluding activity").
      */
-    val isKeyguardShowing: Flow<Boolean>
+    val isKeyguardShowing: StateFlow<Boolean>
 
     /** Is an activity showing over the keyguard? */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
-    val isKeyguardOccluded: Flow<Boolean>
+    val isKeyguardOccluded: StateFlow<Boolean>
 
     /**
      * Whether the device is locked or unlocked right now. This is true when keyguard has been
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 2a8bb47..13d54ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -36,8 +36,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
 
 @SysUISingleton
@@ -73,15 +71,11 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private val canDismissLockscreen: Flow<Boolean> =
-        combine(
-            keyguardInteractor.isKeyguardShowing,
-            keyguardInteractor.isKeyguardDismissible,
-            keyguardInteractor.biometricUnlockState,
-        ) { isKeyguardShowing, isKeyguardDismissible, biometricUnlockState ->
-            (isWakeAndUnlock(biometricUnlockState.mode) ||
-                (!isKeyguardShowing && isKeyguardDismissible))
-        }
+    private fun canDismissLockscreen(): Boolean {
+        return isWakeAndUnlock(keyguardInteractor.biometricUnlockState.value.mode) ||
+            (!keyguardInteractor.isKeyguardShowing.value &&
+                keyguardInteractor.isKeyguardDismissible.value)
+    }
 
     /**
      * Listen for the signal that we're waking up and figure what state we need to transition to.
@@ -96,22 +90,18 @@
                 .debounce(50L)
                 .sample(
                     startedKeyguardTransitionStep,
-                    keyguardInteractor.biometricUnlockState,
-                    keyguardInteractor.primaryBouncerShowing,
-                    keyguardInteractor.isKeyguardOccluded,
-                    canDismissLockscreen,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
                 )
                 .collect {
                     (
                         _,
                         startedStep,
-                        biometricUnlockState,
-                        primaryBouncerShowing,
-                        isKeyguardOccludedLegacy,
-                        canDismissLockscreen,
                         canWakeDirectlyToGone,
                     ) ->
+                    val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
+                    val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode
+                    val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+
                     if (!maybeHandleInsecurePowerGesture()) {
                         val shouldTransitionToLockscreen =
                             if (KeyguardWmStateRefactor.isEnabled) {
@@ -121,12 +111,10 @@
                                 // completes.
                                 !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
                                     startTransitionTo(state, ownerReason = reason)
-                                } &&
-                                    !isWakeAndUnlock(biometricUnlockState.mode) &&
-                                    !primaryBouncerShowing
+                                } && !isWakeAndUnlock(biometricUnlockMode) && !primaryBouncerShowing
                             } else {
                                 !isKeyguardOccludedLegacy &&
-                                    !isWakeAndUnlock(biometricUnlockState.mode) &&
+                                    !isWakeAndUnlock(biometricUnlockMode) &&
                                     !primaryBouncerShowing
                             }
 
@@ -136,7 +124,7 @@
                             !KeyguardWmStateRefactor.isEnabled && isKeyguardOccludedLegacy
 
                         val shouldTransitionToGone =
-                            (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
+                            (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen()) ||
                                 (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
 
                         if (shouldTransitionToGone) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 61446c1..0c12f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -42,8 +42,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.launch
 
@@ -83,13 +81,10 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private val canTransitionToGoneOnWake: Flow<Boolean> =
-        combine(
-            keyguardInteractor.isKeyguardShowing,
-            keyguardInteractor.isKeyguardDismissible,
-        ) { isKeyguardShowing, isKeyguardDismissible ->
-            isKeyguardDismissible && !isKeyguardShowing
-        }
+    private fun canDismissLockscreen(): Boolean {
+        return !keyguardInteractor.isKeyguardShowing.value &&
+            keyguardInteractor.isKeyguardDismissible.value
+    }
 
     private fun listenForDozingToGoneViaBiometrics() {
         if (KeyguardWmStateRefactor.isEnabled) {
@@ -135,27 +130,20 @@
                 .debounce(50L)
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
-                    keyguardInteractor.isKeyguardOccluded,
                     communalInteractor.isCommunalAvailable,
                     communalSceneInteractor.isIdleOnCommunal,
-                    canTransitionToGoneOnWake,
-                    keyguardInteractor.primaryBouncerShowing,
                 )
-                .collect {
-                    (
-                        _,
-                        occluded,
-                        isCommunalAvailable,
-                        isIdleOnCommunal,
-                        canTransitionToGoneOnWake,
-                        primaryBouncerShowing) ->
+                .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
+                    val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
+                    val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+
                     if (!deviceEntryInteractor.isLockscreenEnabled()) {
                         if (SceneContainerFlag.isEnabled) {
                             // TODO(b/336576536): Check if adaptation for scene framework is needed
                         } else {
                             startTransitionTo(KeyguardState.GONE)
                         }
-                    } else if (canTransitionToGoneOnWake) {
+                    } else if (canDismissLockscreen()) {
                         if (SceneContainerFlag.isEnabled) {
                             // TODO(b/336576536): Check if adaptation for scene framework is needed
                         } else {
@@ -167,7 +155,7 @@
                         } else {
                             startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                         }
-                    } else if (occluded) {
+                    } else if (isKeyguardOccludedLegacy) {
                         startTransitionTo(KeyguardState.OCCLUDED)
                     } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
                         if (SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 17c1e82..7bf9c2f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -208,15 +207,15 @@
 
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sampleCombine(
-                    keyguardInteractor.isKeyguardShowing,
-                    keyguardInteractor.isKeyguardDismissible,
-                )
-                .filterRelevantKeyguardStateAnd {
-                    (isDreaming, isKeyguardShowing, isKeyguardDismissible) ->
-                    !isDreaming && isKeyguardDismissible && !isKeyguardShowing
+                .filterRelevantKeyguardStateAnd { isDreaming -> !isDreaming }
+                .collect {
+                    if (
+                        keyguardInteractor.isKeyguardDismissible.value &&
+                            !keyguardInteractor.isKeyguardShowing.value
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-                .collect { startTransitionTo(KeyguardState.GONE) }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 0df989e..4cab2bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -216,14 +216,14 @@
 
     /** Whether the keyguard is showing or not. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
-    val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    val isKeyguardShowing: StateFlow<Boolean> = repository.isKeyguardShowing
 
     /** Whether the keyguard is dismissible or not. */
     val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible
 
     /** Whether the keyguard is occluded (covered by an activity). */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
-    val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
+    val isKeyguardOccluded: StateFlow<Boolean> = repository.isKeyguardOccluded
 
     /** Whether the keyguard is going away. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
@@ -253,7 +253,7 @@
     val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow()
 
     /** Whether the primary bouncer is showing or not. */
-    @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
+    @JvmField val primaryBouncerShowing: StateFlow<Boolean> = bouncerRepository.primaryBouncerShow
 
     /** Whether the alternate bouncer is showing or not. */
     val alternateBouncerShowing: Flow<Boolean> =
@@ -274,7 +274,7 @@
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
 
     /** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */
-    val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+    val biometricUnlockState: StateFlow<BiometricUnlockModel> = repository.biometricUnlockState
 
     /** Keyguard is present and is not occluded. */
     val isKeyguardVisible: Flow<Boolean> =
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 aab5b9b..89851db 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
@@ -152,6 +152,62 @@
                             }
                         }
                     }
+
+                    if (
+                        KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
+                    ) {
+                        launch("$TAG#alpha") {
+                            viewModel.alpha(viewState).collect { alpha ->
+                                view.alpha = alpha
+                                if (KeyguardBottomAreaRefactor.isEnabled) {
+                                    childViews[statusViewId]?.alpha = alpha
+                                    childViews[burnInLayerId]?.alpha = alpha
+                                }
+                            }
+                        }
+                    }
+
+                    if (MigrateClocksToBlueprint.isEnabled) {
+                        launch("$TAG#translationY") {
+                            // When translation happens in burnInLayer, it won't be weather clock
+                            // large clock isn't added to burnInLayer due to its scale transition
+                            // so we also need to add translation to it here
+                            // same as translationX
+                            viewModel.translationY.collect { y ->
+                                childViews[burnInLayerId]?.translationY = y
+                                childViews[largeClockId]?.translationY = y
+                                childViews[aodNotificationIconContainerId]?.translationY = y
+                            }
+                        }
+
+                        launch("$TAG#translationX") {
+                            viewModel.translationX.collect { state ->
+                                val px = state.value ?: return@collect
+                                when {
+                                    state.isToOrFrom(KeyguardState.AOD) -> {
+                                        // Large Clock is not translated in the x direction
+                                        childViews[burnInLayerId]?.translationX = px
+                                        childViews[aodNotificationIconContainerId]?.translationX =
+                                            px
+                                    }
+                                    state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+                                        for ((key, childView) in childViews.entries) {
+                                            when (key) {
+                                                indicationArea,
+                                                startButton,
+                                                endButton,
+                                                lockIcon,
+                                                deviceEntryIcon -> {
+                                                    // Do not move these views
+                                                }
+                                                else -> childView.translationX = px
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
                 }
             }
         disposables +=
@@ -188,20 +244,6 @@
                         }
                     }
 
-                    if (
-                        KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
-                    ) {
-                        launch {
-                            viewModel.alpha(viewState).collect { alpha ->
-                                view.alpha = alpha
-                                if (KeyguardBottomAreaRefactor.isEnabled) {
-                                    childViews[statusViewId]?.alpha = alpha
-                                    childViews[burnInLayerId]?.alpha = alpha
-                                }
-                            }
-                        }
-                    }
-
                     if (MigrateClocksToBlueprint.isEnabled) {
                         launch {
                             viewModel.burnInLayerVisibility.collect { visibility ->
@@ -222,46 +264,6 @@
                         }
 
                         launch {
-                            // When translation happens in burnInLayer, it won't be weather clock
-                            // large clock isn't added to burnInLayer due to its scale transition
-                            // so we also need to add translation to it here
-                            // same as translationX
-                            viewModel.translationY.collect { y ->
-                                childViews[burnInLayerId]?.translationY = y
-                                childViews[largeClockId]?.translationY = y
-                                childViews[aodNotificationIconContainerId]?.translationY = y
-                            }
-                        }
-
-                        launch {
-                            viewModel.translationX.collect { state ->
-                                val px = state.value ?: return@collect
-                                when {
-                                    state.isToOrFrom(KeyguardState.AOD) -> {
-                                        // Large Clock is not translated in the x direction
-                                        childViews[burnInLayerId]?.translationX = px
-                                        childViews[aodNotificationIconContainerId]?.translationX =
-                                            px
-                                    }
-                                    state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
-                                        for ((key, childView) in childViews.entries) {
-                                            when (key) {
-                                                indicationArea,
-                                                startButton,
-                                                endButton,
-                                                lockIcon,
-                                                deviceEntryIcon -> {
-                                                    // Do not move these views
-                                                }
-                                                else -> childView.translationX = px
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-
-                        launch {
                             viewModel.scale.collect { scaleViewModel ->
                                 if (scaleViewModel.scaleClockOnly) {
                                     // For clocks except weather clock, we have scale transition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 050ef6f..06f77bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -270,7 +270,7 @@
                         occludedToLockscreenTransitionViewModel.lockscreenAlpha,
                         primaryBouncerToAodTransitionViewModel.lockscreenAlpha,
                         primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
-                        primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
+                        primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                     )
                     .onStart { emit(1f) }
             ) { hideKeyguard, alpha ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 7511101..d29f512 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
@@ -58,7 +59,14 @@
             onStep = { it }
         )
 
-    val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var currentAlpha = 0f
+        return transitionAnimation.sharedFlow(
+            duration = 250.milliseconds,
+            onStart = { currentAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(currentAlpha, 1f, it) },
+        )
+    }
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
deleted file mode 100644
index e4bafcd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
-import android.text.TextUtils
-import android.util.ArraySet
-import android.util.Log
-import androidx.annotation.GuardedBy
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Dumpable
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.UserAwareController
-import com.android.systemui.util.settings.SecureSettings
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-private const val TAG = "AutoAddTracker"
-private const val DELIMITER = ","
-
-/**
- * Class to track tiles that have been auto-added
- *
- * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
- *
- * It also handles restore gracefully.
- */
-class AutoAddTracker
-@VisibleForTesting
-constructor(
-    private val secureSettings: SecureSettings,
-    private val broadcastDispatcher: BroadcastDispatcher,
-    private val qsHost: QSHost,
-    private val dumpManager: DumpManager,
-    private val mainHandler: Handler?,
-    private val backgroundExecutor: Executor,
-    private var userId: Int
-) : UserAwareController, Dumpable {
-
-    companion object {
-        private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
-    }
-
-    @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>()
-    private var restoredTiles: Map<String, AutoTile>? = null
-
-    override val currentUserId: Int
-        get() = userId
-
-    private val contentObserver =
-        object : ContentObserver(mainHandler) {
-            override fun onChange(
-                selfChange: Boolean,
-                uris: Collection<Uri>,
-                flags: Int,
-                _userId: Int
-            ) {
-                if (_userId != userId) {
-                    // Ignore changes outside of our user. We'll load the correct value on user
-                    // change
-                    return
-                }
-                loadTiles()
-            }
-        }
-
-    private val restoreReceiver =
-        object : BroadcastReceiver() {
-            override fun onReceive(context: Context, intent: Intent) {
-                if (intent.action != Intent.ACTION_SETTING_RESTORED) return
-                processRestoreIntent(intent)
-            }
-        }
-
-    private fun processRestoreIntent(intent: Intent) {
-        when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
-            Settings.Secure.QS_TILES -> {
-                restoredTiles =
-                    intent
-                        .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
-                        ?.split(DELIMITER)
-                        ?.mapIndexed(::AutoTile)
-                        ?.associateBy(AutoTile::tileType)
-                        ?: run {
-                            Log.w(TAG, "Null restored tiles for user $userId")
-                            emptyMap()
-                        }
-            }
-            Settings.Secure.QS_AUTO_ADDED_TILES -> {
-                restoredTiles?.let { restoredTiles ->
-                    val restoredAutoAdded =
-                        intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER)
-                            ?: emptyList()
-                    val autoAddedBeforeRestore =
-                        intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER)
-                            ?: emptyList()
-
-                    val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
-                    if (tilesToRemove.isNotEmpty()) {
-                        Log.d(TAG, "Removing tiles: $tilesToRemove")
-                        qsHost.removeTiles(tilesToRemove)
-                    }
-                    val tiles =
-                        synchronized(autoAdded) {
-                            autoAdded.clear()
-                            autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
-                            getTilesFromListLocked()
-                        }
-                    saveTiles(tiles)
-                }
-                    ?: run {
-                        Log.w(
-                            TAG,
-                            "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
-                                "${Settings.Secure.QS_TILES} for user $userId"
-                        )
-                    }
-            }
-            else -> {} // Do nothing for other Settings
-        }
-    }
-
-    /** Init method must be called after construction to start listening */
-    fun initialize() {
-        dumpManager.registerDumpable(TAG, this)
-        loadTiles()
-        secureSettings.registerContentObserverForUserSync(
-            secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
-            contentObserver,
-            UserHandle.USER_ALL
-        )
-        registerBroadcastReceiver()
-    }
-
-    /** Unregister listeners, receivers and observers */
-    fun destroy() {
-        dumpManager.unregisterDumpable(TAG)
-        secureSettings.unregisterContentObserverSync(contentObserver)
-        unregisterBroadcastReceiver()
-    }
-
-    private fun registerBroadcastReceiver() {
-        broadcastDispatcher.registerReceiver(
-            restoreReceiver,
-            FILTER,
-            backgroundExecutor,
-            UserHandle.of(userId)
-        )
-    }
-
-    private fun unregisterBroadcastReceiver() {
-        broadcastDispatcher.unregisterReceiver(restoreReceiver)
-    }
-
-    override fun changeUser(newUser: UserHandle) {
-        if (newUser.identifier == userId) return
-        unregisterBroadcastReceiver()
-        userId = newUser.identifier
-        restoredTiles = null
-        loadTiles()
-        registerBroadcastReceiver()
-    }
-
-    fun getRestoredTilePosition(tile: String): Int =
-        restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
-
-    /** Returns `true` if the tile has been auto-added before */
-    fun isAdded(tile: String): Boolean {
-        return synchronized(autoAdded) { tile in autoAdded }
-    }
-
-    /**
-     * Sets a tile as auto-added.
-     *
-     * From here on, [isAdded] will return true for that tile.
-     */
-    fun setTileAdded(tile: String) {
-        val tiles =
-            synchronized(autoAdded) {
-                if (autoAdded.add(tile)) {
-                    getTilesFromListLocked()
-                } else {
-                    null
-                }
-            }
-        tiles?.let { saveTiles(it) }
-    }
-
-    /**
-     * Removes a tile from the list of auto-added.
-     *
-     * This allows for this tile to be auto-added again in the future.
-     */
-    fun setTileRemoved(tile: String) {
-        val tiles =
-            synchronized(autoAdded) {
-                if (autoAdded.remove(tile)) {
-                    getTilesFromListLocked()
-                } else {
-                    null
-                }
-            }
-        tiles?.let { saveTiles(it) }
-    }
-
-    private fun getTilesFromListLocked(): String {
-        return TextUtils.join(DELIMITER, autoAdded)
-    }
-
-    private fun saveTiles(tiles: String) {
-        secureSettings.putStringForUser(
-            Settings.Secure.QS_AUTO_ADDED_TILES,
-            tiles,
-            /* tag */ null,
-            /* makeDefault */ false,
-            userId,
-            /* overrideableByRestore */ true
-        )
-    }
-
-    private fun loadTiles() {
-        synchronized(autoAdded) {
-            autoAdded.clear()
-            autoAdded.addAll(getAdded())
-        }
-    }
-
-    private fun getAdded(): Collection<String> {
-        val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
-        return current?.split(DELIMITER) ?: emptySet()
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("Current user: $userId")
-        pw.println("Restored tiles: $restoredTiles")
-        pw.println("Added tiles: $autoAdded")
-    }
-
-    @SysUISingleton
-    class Builder
-    @Inject
-    constructor(
-        private val secureSettings: SecureSettings,
-        private val broadcastDispatcher: BroadcastDispatcher,
-        private val qsHost: QSHost,
-        private val dumpManager: DumpManager,
-        @Main private val handler: Handler,
-        @Background private val executor: Executor
-    ) {
-        private var userId: Int = 0
-
-        fun setUserId(_userId: Int): Builder {
-            userId = _userId
-            return this
-        }
-
-        fun build(): AutoAddTracker {
-            return AutoAddTracker(
-                secureSettings,
-                broadcastDispatcher,
-                qsHost,
-                dumpManager,
-                handler,
-                executor,
-                userId
-            )
-        }
-    }
-
-    private data class AutoTile(val index: Int, val tileType: String)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index c77233e..4323b31 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -47,12 +47,10 @@
 class QSHostAdapter
 @Inject
 constructor(
-    private val qsTileHost: QSTileHost,
     private val interactor: CurrentTilesInteractor,
     private val context: Context,
     private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
     @Application private val scope: CoroutineScope,
-    flags: QSPipelineFlagsRepository,
     dumpManager: DumpManager,
 ) : QSHost {
 
@@ -60,123 +58,69 @@
         private const val TAG = "QSTileHost"
     }
 
-    private val useNewHost = flags.pipelineEnabled
-
     @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
 
     init {
         scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
         // Redirect dump to the correct host (needed for CTS tests)
-        dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
+        dumpManager.registerCriticalDumpable(TAG, interactor)
     }
 
     override fun getTiles(): Collection<QSTile> {
-        return if (useNewHost) {
-            interactor.currentQSTiles
-        } else {
-            qsTileHost.getTiles()
-        }
+        return interactor.currentQSTiles
     }
 
     override fun getSpecs(): List<String> {
-        return if (useNewHost) {
-            interactor.currentTilesSpecs.map { it.spec }
-        } else {
-            qsTileHost.getSpecs()
-        }
+        return interactor.currentTilesSpecs.map { it.spec }
     }
 
     override fun removeTile(spec: String) {
-        if (useNewHost) {
-            interactor.removeTiles(listOf(TileSpec.create(spec)))
-        } else {
-            qsTileHost.removeTile(spec)
-        }
+        interactor.removeTiles(listOf(TileSpec.create(spec)))
     }
 
     override fun addCallback(callback: QSHost.Callback) {
-        if (useNewHost) {
-            val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
-            synchronized(callbacksMap) { callbacksMap.put(callback, job) }
-        } else {
-            qsTileHost.addCallback(callback)
-        }
+        val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
+        synchronized(callbacksMap) { callbacksMap.put(callback, job) }
     }
 
     override fun removeCallback(callback: QSHost.Callback) {
-        if (useNewHost) {
-            synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() }
-        } else {
-            qsTileHost.removeCallback(callback)
-        }
+        synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() }
     }
 
     override fun removeTiles(specs: Collection<String>) {
-        if (useNewHost) {
-            interactor.removeTiles(specs.map(TileSpec::create))
-        } else {
-            qsTileHost.removeTiles(specs)
-        }
+        interactor.removeTiles(specs.map(TileSpec::create))
     }
 
     override fun removeTileByUser(component: ComponentName) {
-        if (useNewHost) {
-            interactor.removeTiles(listOf(TileSpec.create(component)))
-        } else {
-            qsTileHost.removeTileByUser(component)
-        }
+        interactor.removeTiles(listOf(TileSpec.create(component)))
     }
 
     override fun addTile(spec: String, position: Int) {
-        if (useNewHost) {
-            interactor.addTile(TileSpec.create(spec), position)
-        } else {
-            qsTileHost.addTile(spec, position)
-        }
+        interactor.addTile(TileSpec.create(spec), position)
     }
 
     override fun addTile(component: ComponentName, end: Boolean) {
-        if (useNewHost) {
-            interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
-        } else {
-            qsTileHost.addTile(component, end)
-        }
+        interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
     }
 
     override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
-        if (useNewHost) {
-            interactor.setTiles(newTiles.map(TileSpec::create))
-        } else {
-            qsTileHost.changeTilesByUser(previousTiles, newTiles)
-        }
+        interactor.setTiles(newTiles.map(TileSpec::create))
     }
 
     override fun getContext(): Context {
-        return if (useNewHost) {
-            context
-        } else {
-            qsTileHost.context
-        }
+        return context
     }
 
     override fun getUserContext(): Context {
-        return if (useNewHost) {
-            interactor.userContext.value
-        } else {
-            qsTileHost.userContext
-        }
+        return interactor.userContext.value
     }
 
     override fun getUserId(): Int {
-        return if (useNewHost) {
-            interactor.userId.value
-        } else {
-            qsTileHost.userId
-        }
+        return interactor.userId.value
     }
 
     override fun createTile(tileSpec: String): QSTile? {
-        return qsTileHost.createTile(tileSpec)
+        return interactor.createTileSync(TileSpec.create(tileSpec))
     }
 
     override fun addTile(spec: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
deleted file mode 100644
index 03c2aa6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dumpable;
-import com.android.systemui.ProtoDumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.nano.QsTileState;
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.qs.tiles.di.NewQSTileFactory;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Lazy;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/** Platform implementation of the quick settings tile host
- *
- * This class keeps track of the set of current tiles and is the in memory source of truth
- * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes,
- * {@link #onTuningChanged} will be called and the tiles will be re-created as needed.
- *
- * This class also provides the interface for adding/removing/changing tiles.
- */
-@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
-        PanelInteractor, CustomTileAddedRepository {
-    private static final String TAG = "QSTileHost";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    // Shared prefs that hold tile lifecycle info.
-    @VisibleForTesting
-    static final String TILES = "tiles_prefs";
-
-    private final Context mContext;
-    private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
-    private final ArrayList<String> mTileSpecs = new ArrayList<>();
-    private final TunerService mTunerService;
-    private final PluginManager mPluginManager;
-    private final QSLogger mQSLogger;
-    private final CustomTileStatePersister mCustomTileStatePersister;
-    private final Executor mMainExecutor;
-    private final UserFileManager mUserFileManager;
-
-    private final List<Callback> mCallbacks = new ArrayList<>();
-    @Nullable
-    private AutoTileManager mAutoTiles;
-    private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
-    private int mCurrentUser;
-    private final Lazy<ShadeController> mShadeControllerProvider;
-    private Context mUserContext;
-    private UserTracker mUserTracker;
-    private SecureSettings mSecureSettings;
-    // Keep track of whether mTilesList contains the same information as the Settings value.
-    // This is a performance optimization to reduce the number of blocking calls to Settings from
-    // main thread.
-    // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
-    private boolean mTilesListDirty = true;
-
-    private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
-
-    private final QSPipelineFlagsRepository mFeatureFlags;
-
-    @Inject
-    public QSTileHost(Context context,
-            Lazy<NewQSTileFactory> newQsTileFactoryProvider,
-            QSFactory defaultFactory,
-            @Main Executor mainExecutor,
-            PluginManager pluginManager,
-            TunerService tunerService,
-            Provider<AutoTileManager> autoTiles,
-            Lazy<ShadeController> shadeControllerProvider,
-            QSLogger qsLogger,
-            UserTracker userTracker,
-            SecureSettings secureSettings,
-            CustomTileStatePersister customTileStatePersister,
-            TileLifecycleManager.Factory tileLifecycleManagerFactory,
-            UserFileManager userFileManager,
-            QSPipelineFlagsRepository featureFlags
-    ) {
-        mContext = context;
-        mUserContext = context;
-        mTunerService = tunerService;
-        mPluginManager = pluginManager;
-        mQSLogger = qsLogger;
-        mMainExecutor = mainExecutor;
-        mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
-        mUserFileManager = userFileManager;
-        mFeatureFlags = featureFlags;
-
-        mShadeControllerProvider = shadeControllerProvider;
-
-        if (featureFlags.getTilesEnabled()) {
-            mQsFactories.add(newQsTileFactoryProvider.get());
-        }
-        mQsFactories.add(defaultFactory);
-        pluginManager.addPluginListener(this, QSFactory.class, true);
-        mUserTracker = userTracker;
-        mCurrentUser = userTracker.getUserId();
-        mSecureSettings = secureSettings;
-        mCustomTileStatePersister = customTileStatePersister;
-
-        mainExecutor.execute(() -> {
-            // This is technically a hack to avoid circular dependency of
-            // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
-            // finishes before creating any tiles.
-            tunerService.addTunable(this, TILES_SETTING);
-            // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
-            if (!mFeatureFlags.getPipelineEnabled()) {
-                mAutoTiles = autoTiles.get();
-            }
-        });
-    }
-
-    public void destroy() {
-        mTiles.values().forEach(tile -> tile.destroy());
-        mAutoTiles.destroy();
-        mTunerService.removeTunable(this);
-        mPluginManager.removePluginListener(this);
-    }
-
-    @Override
-    public void onPluginConnected(QSFactory plugin, Context pluginContext) {
-        // Give plugins priority over creation so they can override if they wish.
-        mQsFactories.add(0, plugin);
-        String value = mTunerService.getValue(TILES_SETTING);
-        // Force remove and recreate of all tiles.
-        onTuningChanged(TILES_SETTING, "");
-        onTuningChanged(TILES_SETTING, value);
-    }
-
-    @Override
-    public void onPluginDisconnected(QSFactory plugin) {
-        mQsFactories.remove(plugin);
-        // Force remove and recreate of all tiles.
-        String value = mTunerService.getValue(TILES_SETTING);
-        onTuningChanged(TILES_SETTING, "");
-        onTuningChanged(TILES_SETTING, value);
-    }
-
-    @Override
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-    }
-
-    @Override
-    public void removeCallback(Callback callback) {
-        mCallbacks.remove(callback);
-    }
-
-    @Override
-    public Collection<QSTile> getTiles() {
-        return mTiles.values();
-    }
-
-    @Override
-    public void collapsePanels() {
-        mShadeControllerProvider.get().postAnimateCollapseShade();
-    }
-
-    @Override
-    public void forceCollapsePanels() {
-        mShadeControllerProvider.get().postAnimateForceCollapseShade();
-    }
-
-    @Override
-    public void openPanels() {
-        mShadeControllerProvider.get().postAnimateExpandQs();
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public Context getUserContext() {
-        return mUserContext;
-    }
-
-    @Override
-    public int getUserId() {
-        return mCurrentUser;
-    }
-
-    public int indexOf(String spec) {
-        return mTileSpecs.indexOf(spec);
-    }
-
-    /**
-     * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this
-     * will be called with the new value of the setting.
-     *
-     * This method will do the following:
-     * <ol>
-     *     <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li>
-     *     <li>Create new tiles for those that don't already exist. If this tiles end up being
-     *         not available, they'll also be destroyed.</li>
-     *     <li>Save the resolved list of tiles (current tiles that are available) into the setting.
-     *         This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs},
-     *         and visible tiles ({@link #mTiles}) must match.
-     *         </li>
-     * </ol>
-     *
-     * Additionally, if the user has changed, it'll do the following:
-     * <ul>
-     *     <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li>
-     *     <li>Destroy any {@link CustomTile} and recreate it for the new user.</li>
-     * </ul>
-     *
-     * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches
-     * in main thread.
-     *
-     * @see QSTile#isAvailable
-     */
-    @MainThread
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (!TILES_SETTING.equals(key)) {
-            return;
-        }
-        int currentUser = mUserTracker.getUserId();
-        if (currentUser != mCurrentUser) {
-            mUserContext = mUserTracker.getUserContext();
-            if (mAutoTiles != null) {
-                mAutoTiles.changeUser(UserHandle.of(currentUser));
-            }
-        }
-        // Do not process tiles if the flag is enabled.
-        if (mFeatureFlags.getPipelineEnabled()) {
-            return;
-        }
-        QSPipelineFlagsRepository.Utils.assertInLegacyMode();
-        if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
-            newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
-        }
-        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
-        Log.d(TAG, "Recreating tiles: " + tileSpecs);
-        mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
-                tile -> {
-                    Log.d(TAG, "Destroying tile: " + tile.getKey());
-                    mQSLogger.logTileDestroyed(tile.getKey(), "Tile removed");
-                    tile.getValue().destroy();
-                });
-        final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
-        for (String tileSpec : tileSpecs) {
-            QSTile tile = mTiles.get(tileSpec);
-            if (tile != null && (!(tile instanceof CustomTile)
-                    || ((CustomTile) tile).getUser() == currentUser)) {
-                if (tile.isAvailable()) {
-                    Log.d(TAG, "Adding " + tile);
-                    tile.removeCallbacks();
-                    if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
-                        tile.userSwitch(currentUser);
-                    }
-                    newTiles.put(tileSpec, tile);
-                    mQSLogger.logTileAdded(tileSpec);
-                } else {
-                    tile.destroy();
-                    Log.d(TAG, "Destroying not available tile: " + tileSpec);
-                    mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
-                }
-            } else {
-                // This means that the tile is a CustomTile AND the user is different, so let's
-                // destroy it
-                if (tile != null) {
-                    tile.destroy();
-                    Log.d(TAG, "Destroying tile for wrong user: " + tileSpec);
-                    mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user");
-                }
-                Log.d(TAG, "Creating tile: " + tileSpec);
-                try {
-                    tile = createTile(tileSpec);
-                    if (tile != null) {
-                        if (tile.isAvailable()) {
-                            newTiles.put(tileSpec, tile);
-                            mQSLogger.logTileAdded(tileSpec);
-                        } else {
-                            tile.destroy();
-                            Log.d(TAG, "Destroying not available tile: " + tileSpec);
-                            mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
-                        }
-                    } else {
-                        Log.d(TAG, "No factory for a spec: " + tileSpec);
-                    }
-                } catch (Throwable t) {
-                    Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
-                }
-            }
-        }
-        mCurrentUser = currentUser;
-        List<String> currentSpecs = new ArrayList<>(mTileSpecs);
-        mTileSpecs.clear();
-        mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
-        mTiles.clear();
-        mTiles.putAll(newTiles);
-        if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
-            // If we didn't manage to create any tiles, set it to empty (default)
-            Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
-            changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
-        } else {
-            String resolvedTiles = TextUtils.join(",", mTileSpecs);
-            if (!resolvedTiles.equals(newValue)) {
-                // If the resolved tiles (those we actually ended up with) are different than
-                // the ones that are in the setting, update the Setting.
-                saveTilesToSettings(mTileSpecs);
-            }
-            mTilesListDirty = false;
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                mCallbacks.get(i).onTilesChanged();
-            }
-        }
-    }
-
-    /**
-     * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need
-     * its lifecycle terminated).
-     */
-    @Override
-    public void removeTile(String spec) {
-        if (spec.startsWith(CustomTile.PREFIX)) {
-            // If the tile is removed (due to it not actually existing), mark it as removed. That
-            // way it will be marked as newly added if it appears in the future.
-            setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
-        }
-        mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
-    }
-
-    /**
-     * Remove many tiles at once.
-     *
-     * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called
-     * multiple times).
-     */
-    @Override
-    public void removeTiles(Collection<String> specs) {
-        mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
-    }
-
-    /**
-     * Add a tile to the end
-     *
-     * @param spec string matching a pre-defined tilespec
-     */
-    public void addTile(String spec) {
-        addTile(spec, POSITION_AT_END);
-    }
-
-    @Override
-    public void addTile(String spec, int requestPosition) {
-        mMainExecutor.execute(() ->
-                changeTileSpecs(tileSpecs -> {
-                    if (tileSpecs.contains(spec)) return false;
-
-                    int size = tileSpecs.size();
-                    if (requestPosition == POSITION_AT_END || requestPosition >= size) {
-                        tileSpecs.add(spec);
-                    } else {
-                        tileSpecs.add(requestPosition, spec);
-                    }
-                    return true;
-                })
-        );
-    }
-
-    // When calling this, you may want to modify mTilesListDirty accordingly.
-    @MainThread
-    private void saveTilesToSettings(List<String> tileSpecs) {
-        Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser);
-        mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
-                null /* tag */, false /* default */, mCurrentUser,
-                true /* overrideable by restore */);
-    }
-
-    @MainThread
-    private void changeTileSpecs(Predicate<List<String>> changeFunction) {
-        final List<String> tileSpecs;
-        if (!mTilesListDirty) {
-            tileSpecs = new ArrayList<>(mTileSpecs);
-        } else {
-            tileSpecs = loadTileSpecs(mContext,
-                    mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser));
-        }
-        if (changeFunction.test(tileSpecs)) {
-            mTilesListDirty = true;
-            saveTilesToSettings(tileSpecs);
-        }
-    }
-
-    @Override
-    public void addTile(ComponentName tile) {
-        addTile(tile, /* end */ false);
-    }
-
-    @Override
-    public void addTile(ComponentName tile, boolean end) {
-        String spec = CustomTile.toSpec(tile);
-        addTile(spec, end ? POSITION_AT_END : 0);
-    }
-
-    /**
-     * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
-     * removed by a <b>user action</b> like {@code adb}.
-     */
-    @Override
-    public void removeTileByUser(ComponentName tile) {
-        mMainExecutor.execute(() -> {
-            List<String> newSpecs = new ArrayList<>(mTileSpecs);
-            if (newSpecs.remove(CustomTile.toSpec(tile))) {
-                changeTilesByUser(mTileSpecs, newSpecs);
-            }
-        });
-    }
-
-    /**
-     * Change the tiles triggered by the user editing.
-     * <p>
-     * This is not called on device start, or on user change.
-     *
-     * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles
-     * that are removed.
-     */
-    @MainThread
-    @Override
-    public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
-        final List<String> copy = new ArrayList<>(previousTiles);
-        final int NP = copy.size();
-        for (int i = 0; i < NP; i++) {
-            String tileSpec = copy.get(i);
-            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
-            if (!newTiles.contains(tileSpec)) {
-                ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
-                Intent intent = new Intent().setComponent(component);
-                TileLifecycleManager lifecycleManager = mTileLifeCycleManagerFactory.create(
-                        intent, new UserHandle(mCurrentUser));
-                lifecycleManager.onStopListening();
-                lifecycleManager.onTileRemoved();
-                mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
-                setTileAdded(component, mCurrentUser, false);
-                lifecycleManager.flushMessagesAndUnbind();
-            }
-        }
-        Log.d(TAG, "saveCurrentTiles " + newTiles);
-        mTilesListDirty = true;
-        saveTilesToSettings(newTiles);
-    }
-
-    @Nullable
-    @Override
-    public QSTile createTile(String tileSpec) {
-        for (int i = 0; i < mQsFactories.size(); i++) {
-            QSTile t = mQsFactories.get(i).createTile(tileSpec);
-            if (t != null) {
-                return t;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Check if a particular {@link CustomTile} has been added for a user and has not been removed
-     * since.
-     * @param componentName the {@link ComponentName} of the
-     *                      {@link android.service.quicksettings.TileService} associated with the
-     *                      tile.
-     * @param userId the user to check
-     */
-    @Override
-    public boolean isTileAdded(ComponentName componentName, int userId) {
-        return mUserFileManager
-                .getSharedPreferences(TILES, 0, userId)
-                .getBoolean(componentName.flattenToString(), false);
-    }
-
-    /**
-     * Persists whether a particular {@link CustomTile} has been added and it's currently in the
-     * set of selected tiles ({@link #mTiles}.
-     * @param componentName the {@link ComponentName} of the
-     *                      {@link android.service.quicksettings.TileService} associated
-     *                      with the tile.
-     * @param userId the user for this tile
-     * @param added {@code true} if the tile is being added, {@code false} otherwise
-     */
-    @Override
-    public void setTileAdded(ComponentName componentName, int userId, boolean added) {
-        mUserFileManager.getSharedPreferences(TILES, 0, userId)
-                .edit()
-                .putBoolean(componentName.flattenToString(), added)
-                .apply();
-    }
-
-    @Override
-    public List<String> getSpecs() {
-        return mTileSpecs;
-    }
-
-    protected static List<String> loadTileSpecs(Context context, String tileList) {
-        final Resources res = context.getResources();
-
-        if (TextUtils.isEmpty(tileList)) {
-            tileList = res.getString(R.string.quick_settings_tiles);
-            Log.d(TAG, "Loaded tile specs from default config: " + tileList);
-        } else {
-            Log.d(TAG, "Loaded tile specs from setting: " + tileList);
-        }
-        final ArrayList<String> tiles = new ArrayList<String>();
-        boolean addedDefault = false;
-        Set<String> addedSpecs = new ArraySet<>();
-        for (String tile : tileList.split(",")) {
-            tile = tile.trim();
-            if (tile.isEmpty()) continue;
-            if (tile.equals("default")) {
-                if (!addedDefault) {
-                    List<String> defaultSpecs = QSHost.getDefaultSpecs(context.getResources());
-                    for (String spec : defaultSpecs) {
-                        if (!addedSpecs.contains(spec)) {
-                            tiles.add(spec);
-                            addedSpecs.add(spec);
-                        }
-                    }
-                    addedDefault = true;
-                }
-            } else {
-                if (!addedSpecs.contains(tile)) {
-                    tiles.add(tile);
-                    addedSpecs.add(tile);
-                }
-            }
-        }
-
-        if (!tiles.contains("internet")) {
-            if (tiles.contains("wifi")) {
-                // Replace the WiFi with Internet, and remove the Cell
-                tiles.set(tiles.indexOf("wifi"), "internet");
-                tiles.remove("cell");
-            } else if (tiles.contains("cell")) {
-                // Replace the Cell with Internet
-                tiles.set(tiles.indexOf("cell"), "internet");
-            }
-        } else {
-            tiles.remove("wifi");
-            tiles.remove("cell");
-        }
-        return tiles;
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("QSTileHost:");
-        pw.println("tile specs: " + mTileSpecs);
-        pw.println("current user: " + mCurrentUser);
-        pw.println("is dirty: " + mTilesListDirty);
-        pw.println("tiles:");
-        mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
-                .forEach(o -> ((Dumpable) o).dump(pw, args));
-    }
-
-    @Override
-    public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) {
-        List<QsTileState> data = mTiles.values().stream()
-                .map(QSTile::getState)
-                .map(TileStateToProtoKt::toProto)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
-
-        systemUIProtoDump.tiles = data.toArray(new QsTileState[0]);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 496a6f8..a947d36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -18,17 +18,14 @@
 
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QSHostAdapter
-import com.android.systemui.qs.QSTileHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.QsEventLoggerImpl
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
 
 @Module
 interface QSHostModule {
@@ -37,36 +34,10 @@
 
     @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
 
-    @Module
-    companion object {
-        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+    @Binds fun providePanelInteractor(impl: PanelInteractorImpl): PanelInteractor
 
-        @Provides
-        @JvmStatic
-        fun providePanelInteractor(
-            featureFlags: QSPipelineFlagsRepository,
-            qsHost: QSTileHost,
-            panelInteractorImpl: PanelInteractorImpl
-        ): PanelInteractor {
-            return if (featureFlags.pipelineEnabled) {
-                panelInteractorImpl
-            } else {
-                qsHost
-            }
-        }
-
-        @Provides
-        @JvmStatic
-        fun provideCustomTileAddedRepository(
-            featureFlags: QSPipelineFlagsRepository,
-            qsHost: QSTileHost,
-            customTileAddedRepository: CustomTileAddedSharedPrefsRepository
-        ): CustomTileAddedRepository {
-            return if (featureFlags.pipelineEnabled) {
-                customTileAddedRepository
-            } else {
-                qsHost
-            }
-        }
-    }
+    @Binds
+    fun provideCustomTileAddedRepository(
+        impl: CustomTileAddedSharedPrefsRepository
+    ): CustomTileAddedRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index b705a03..29bcad4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -16,17 +16,7 @@
 
 package com.android.systemui.qs.dagger;
 
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dagger.MediaModule;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
 import com.android.systemui.qs.external.QSExternalModule;
@@ -36,24 +26,12 @@
 import com.android.systemui.qs.tiles.di.QSTilesModule;
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter;
 import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
 
 import java.util.Map;
 
-import javax.inject.Named;
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.Multibinds;
 
 /**
  * Module for QS dependencies
@@ -78,45 +56,6 @@
     @Multibinds
     Map<String, QSTileImpl<?>> tileMap();
 
-    @Provides
-    @SysUISingleton
-    static AutoTileManager provideAutoTileManager(
-            Context context,
-            AutoAddTracker.Builder autoAddTrackerBuilder,
-            QSHost host,
-            @Background Handler handler,
-            SecureSettings secureSettings,
-            HotspotController hotspotController,
-            DataSaverController dataSaverController,
-            ManagedProfileController managedProfileController,
-            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
-            CastController castController,
-            ReduceBrightColorsController reduceBrightColorsController,
-            DeviceControlsController deviceControlsController,
-            WalletController walletController,
-            SafetyController safetyController,
-            @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        AutoTileManager manager = new AutoTileManager(
-                context,
-                autoAddTrackerBuilder,
-                host,
-                handler,
-                secureSettings,
-                hotspotController,
-                dataSaverController,
-                managedProfileController,
-                nightDisplayListenerBuilder,
-                castController,
-                reduceBrightColorsController,
-                deviceControlsController,
-                walletController,
-                safetyController,
-                isReduceBrightColorsAvailable
-        );
-        manager.init();
-        return manager;
-    }
-
     @Binds
     QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 02379e6..4a96710 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -116,6 +116,8 @@
      */
     fun setTiles(specs: List<TileSpec>)
 
+    fun createTileSync(spec: TileSpec): QSTile?
+
     companion object {
         val POSITION_AT_END: Int = TileSpecRepository.POSITION_AT_END
     }
@@ -190,9 +192,7 @@
             }
 
     init {
-        if (featureFlags.pipelineEnabled) {
-            startTileCollection()
-        }
+        startTileCollection()
     }
 
     private fun startTileCollection() {
@@ -342,15 +342,16 @@
         lifecycleManager.flushMessagesAndUnbind()
     }
 
+    override fun createTileSync(spec: TileSpec): QSTile? {
+        return if (featureFlags.tilesEnabled) {
+            newQSTileFactory.get().createTile(spec.spec)
+        } else {
+            null
+        } ?: tileFactory.createTile(spec.spec)
+    }
+
     private suspend fun createTile(spec: TileSpec): QSTile? {
-        val tile =
-            withContext(mainDispatcher) {
-                if (featureFlags.tilesEnabled) {
-                    newQSTileFactory.get().createTile(spec.spec)
-                } else {
-                    null
-                } ?: tileFactory.createTile(spec.spec)
-            }
+        val tile = withContext(mainDispatcher) { createTileSync(spec) }
         if (tile == null) {
             logger.logTileNotFoundInFactory(spec)
             return null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index c8fbeb5..0bcb6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -40,14 +40,12 @@
 ) : CoreStartable {
 
     override fun start() {
-        if (featureFlags.pipelineEnabled) {
-            accessibilityTilesInteractor.init(currentTilesInteractor)
-            autoAddInteractor.init(currentTilesInteractor)
-            restoreReconciliationInteractor.start()
+        accessibilityTilesInteractor.init(currentTilesInteractor)
+        autoAddInteractor.init(currentTilesInteractor)
+        restoreReconciliationInteractor.start()
 
-            if (NewQsUI.isEnabled) {
-                gridConsistencyInteractor.start()
-            }
+        if (NewQsUI.isEnabled) {
+            gridConsistencyInteractor.start()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 42bee3c..5dc8d1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -9,19 +9,10 @@
 @SysUISingleton
 class QSPipelineFlagsRepository @Inject constructor() {
 
-    val pipelineEnabled: Boolean
-        get() = AconfigFlags.qsNewPipeline()
-
     val tilesEnabled: Boolean
         get() = AconfigFlags.qsNewTiles()
 
     companion object Utils {
-        fun assertInLegacyMode() =
-            RefactorFlagUtils.assertInLegacyMode(
-                AconfigFlags.qsNewPipeline(),
-                AconfigFlags.FLAG_QS_NEW_PIPELINE
-            )
-
         fun assertNewTiles() =
             RefactorFlagUtils.assertInNewMode(
                 AconfigFlags.qsNewTiles(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
new file mode 100644
index 0000000..af55f5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneBackInteractor
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models the UI state needed to figure out which user actions can trigger navigation from the quick
+ * settings scene to other scenes.
+ *
+ * Different from [QuickSettingsSceneContentViewModel] that models UI state needed for rendering the
+ * content of the quick settings scene.
+ */
+class QuickSettingsSceneActionsViewModel
+@AssistedInject
+constructor(
+    private val qsSceneAdapter: QSSceneAdapter,
+    sceneBackInteractor: SceneBackInteractor,
+) : SceneActionsViewModel() {
+
+    private val backScene: Flow<SceneKey> =
+        sceneBackInteractor.backScene
+            .filter { it != Scenes.QuickSettings }
+            .map { it ?: Scenes.Shade }
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        combine(
+                qsSceneAdapter.isCustomizerShowing,
+                backScene,
+            ) { isCustomizing, backScene ->
+                buildMap<UserAction, UserActionResult> {
+                    if (isCustomizing) {
+                        // TODO(b/332749288) Empty map so there are no back handlers and back can
+                        // close
+                        // customizer
+
+                        // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
+                        // while customizing
+                    } else {
+                        put(Back, UserActionResult(backScene))
+                        put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
+                        put(
+                            Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
+                            UserActionResult(SceneFamilies.Home),
+                        )
+                    }
+                }
+            }
+            .collectLatest { actions -> setActions(actions) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickSettingsSceneActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
new file mode 100644
index 0000000..55b8f5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
+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.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Models UI state needed for rendering the content of the quick settings scene.
+ *
+ * Different from [QuickSettingsSceneActionsViewModel] that models the UI state needed to figure out
+ * which user actions can trigger navigation to other scenes.
+ */
+class QuickSettingsSceneContentViewModel
+@AssistedInject
+constructor(
+    val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
+    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
+    val qsSceneAdapter: QSSceneAdapter,
+    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+    private val footerActionsController: FooterActionsController,
+    val mediaCarouselInteractor: MediaCarouselInteractor,
+) : SysUiViewModel() {
+
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
+
+    private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+    fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+        if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+            footerActionsController.init()
+        }
+        return footerActionsViewModelFactory.create(lifecycleOwner)
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickSettingsSceneContentViewModel
+    }
+}
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
deleted file mode 100644
index 7258882..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.qs.ui.viewmodel
-
-import androidx.lifecycle.LifecycleOwner
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-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.SceneBackInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
-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 java.util.concurrent.atomic.AtomicBoolean
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-
-/** Models UI state and handles user input for the quick settings scene. */
-@SysUISingleton
-class QuickSettingsSceneViewModel
-@Inject
-constructor(
-    val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
-    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
-    val qsSceneAdapter: QSSceneAdapter,
-    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
-    private val footerActionsController: FooterActionsController,
-    sceneBackInteractor: SceneBackInteractor,
-    val mediaCarouselInteractor: MediaCarouselInteractor,
-) {
-    private val backScene: Flow<SceneKey> =
-        sceneBackInteractor.backScene
-            .filter { it != Scenes.QuickSettings }
-            .map { it ?: Scenes.Shade }
-
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        combine(
-            qsSceneAdapter.isCustomizerShowing,
-            backScene,
-        ) { isCustomizing, backScene ->
-            buildMap<UserAction, UserActionResult> {
-                if (isCustomizing) {
-                    // TODO(b/332749288) Empty map so there are no back handlers and back can close
-                    // customizer
-
-                    // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
-                    // while customizing
-                } else {
-                    put(Back, UserActionResult(backScene))
-                    put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
-                    put(
-                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
-                        UserActionResult(SceneFamilies.Home),
-                    )
-                }
-            }
-        }
-
-    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
-
-    private val footerActionsControllerInitialized = AtomicBoolean(false)
-
-    fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
-        if (footerActionsControllerInitialized.compareAndSet(false, true)) {
-            footerActionsController.init()
-        }
-        return footerActionsViewModelFactory.create(lifecycleOwner)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index f6924f2..8aa601f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -26,13 +26,12 @@
         attrs,
     ) {
 
-    private lateinit var viewModel: SceneContainerViewModel
-
+    private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private val windowInsets: MutableStateFlow<WindowInsets?> = MutableStateFlow(null)
 
     fun init(
-        viewModel: SceneContainerViewModel,
+        viewModelFactory: SceneContainerViewModel.Factory,
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
         scenes: Set<Scene>,
@@ -40,11 +39,13 @@
         sceneDataSourceDelegator: SceneDataSourceDelegator,
         alternateBouncerDependencies: AlternateBouncerDependencies,
     ) {
-        this.viewModel = viewModel
         setLayoutInsetsController(layoutInsetController)
         SceneWindowRootViewBinder.bind(
             view = this@SceneWindowRootView,
-            viewModel = viewModel,
+            viewModelFactory = viewModelFactory,
+            motionEventHandlerReceiver = { motionEventHandler ->
+                this.motionEventHandler = motionEventHandler
+            },
             windowInsets = windowInsets,
             containerConfig = containerConfig,
             sharedNotificationContainer = sharedNotificationContainer,
@@ -69,10 +70,10 @@
     }
 
     override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
-        viewModel.onMotionEvent(ev)
+        motionEventHandler?.onMotionEvent(ev)
         return super.dispatchTouchEvent(ev).also {
             TouchLogger.logDispatchTouch(TAG, ev, it)
-            viewModel.onMotionEventComplete()
+            motionEventHandler?.onMotionEventComplete()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 73a8e4c..ad68f17 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -29,8 +29,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.policy.ScreenDecorationsUtils
@@ -39,7 +37,9 @@
 import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
 import com.android.systemui.keyguard.ui.composable.AlternateBouncer
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
+import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scene
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
@@ -63,7 +64,8 @@
     /** Binds between the view and view-model pertaining to a specific scene container. */
     fun bind(
         view: ViewGroup,
-        viewModel: SceneContainerViewModel,
+        viewModelFactory: SceneContainerViewModel.Factory,
+        motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
         windowInsets: StateFlow<WindowInsets?>,
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
@@ -85,8 +87,11 @@
         }
 
         view.repeatWhenAttached {
-            lifecycleScope.launch {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
+            view.viewModel(
+                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+                factory = { viewModelFactory.create(motionEventHandlerReceiver) },
+            ) { viewModel ->
+                try {
                     view.setViewTreeOnBackPressedDispatcherOwner(
                         object : OnBackPressedDispatcherOwner {
                             override val onBackPressedDispatcher =
@@ -140,10 +145,11 @@
                             onVisibilityChangedInternal(isVisible)
                         }
                     }
+                    awaitCancellation()
+                } finally {
+                    // Here when destroyed.
+                    view.removeAllViews()
                 }
-
-                // Here when destroyed.
-                view.removeAllViews()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index a28222e..2d02f5a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -23,25 +23,26 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
 
 /** Models UI state for the scene container. */
-@SysUISingleton
 class SceneContainerViewModel
-@Inject
+@AssistedInject
 constructor(
     private val sceneInteractor: SceneInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
-) {
+    @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
+) : SysUiViewModel() {
     /**
      * Keys of all scenes in the container.
      *
@@ -56,6 +57,29 @@
     /** Whether the container is visible. */
     val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
 
+    override suspend fun onActivated() {
+        try {
+            // Sends a MotionEventHandler to the owner of the view-model so they can report
+            // MotionEvents into the view-model.
+            motionEventHandlerReceiver(
+                object : MotionEventHandler {
+                    override fun onMotionEvent(motionEvent: MotionEvent) {
+                        this@SceneContainerViewModel.onMotionEvent(motionEvent)
+                    }
+
+                    override fun onMotionEventComplete() {
+                        this@SceneContainerViewModel.onMotionEventComplete()
+                    }
+                }
+            )
+            awaitCancellation()
+        } finally {
+            // Clears the previously-sent MotionEventHandler so the owner of the view-model releases
+            // their reference to it.
+            motionEventHandlerReceiver(null)
+        }
+    }
+
     /**
      * Binds the given flow so the system remembers it.
      *
@@ -136,21 +160,22 @@
         }
     }
 
-    private fun replaceSceneFamilies(
-        destinationScenes: Map<UserAction, UserActionResult>,
-    ): Flow<Map<UserAction, UserActionResult>> {
-        return destinationScenes
-            .mapValues { (_, actionResult) ->
-                sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene ->
-                    actionResult.copy(toScene = scene)
-                }
-            }
-            .combineValueFlows()
+    /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
+    interface MotionEventHandler {
+        /** Notifies that a [MotionEvent] has occurred. */
+        fun onMotionEvent(motionEvent: MotionEvent)
+
+        /**
+         * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished
+         * processing.
+         */
+        fun onMotionEventComplete()
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
+        ): SceneContainerViewModel
     }
 }
-
-private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> =
-    combine(
-        asIterable().map { (k, fv) -> fv.map { k to it } },
-        transform = Array<Pair<K, V>>::toMap,
-    )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 21bbaa5..606fef0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -79,7 +79,7 @@
         @SysUISingleton
         fun providesWindowRootView(
             layoutInflater: LayoutInflater,
-            viewModelProvider: Provider<SceneContainerViewModel>,
+            viewModelFactory: SceneContainerViewModel.Factory,
             containerConfigProvider: Provider<SceneContainerConfig>,
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
@@ -91,7 +91,7 @@
                 val sceneWindowRootView =
                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
                 sceneWindowRootView.init(
-                    viewModel = viewModelProvider.get(),
+                    viewModelFactory = viewModelFactory,
                     containerConfig = containerConfigProvider.get(),
                     sharedNotificationContainer =
                         sceneWindowRootView.requireViewById(R.id.shared_notification_container),
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 99f7a75..f63ee7b 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
@@ -511,7 +511,7 @@
                 occludedToAodTransitionViewModel.lockscreenAlpha,
                 occludedToGoneTransitionViewModel.notificationAlpha(viewState),
                 occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-                primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
+                primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                 glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
                 lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
deleted file mode 100644
index a538856..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.NightDisplayListener;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.UserSettingObserver;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastDevice;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DataSaverController.Listener;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.HotspotController.Callback;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.UserAwareController;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-
-import javax.inject.Named;
-
-/**
- * Manages which tiles should be automatically added to QS.
- */
-public class AutoTileManager implements UserAwareController {
-    private static final String TAG = "AutoTileManager";
-
-    public static final String HOTSPOT = "hotspot";
-    public static final String SAVER = "saver";
-    public static final String INVERSION = "inversion";
-    public static final String WORK = "work";
-    public static final String NIGHT = "night";
-    public static final String CAST = "cast";
-    public static final String DEVICE_CONTROLS = "controls";
-    public static final String WALLET = "wallet";
-    public static final String BRIGHTNESS = "reduce_brightness";
-    static final String SETTING_SEPARATOR = ":";
-
-    private UserHandle mCurrentUser;
-    private boolean mInitialized;
-    private final String mSafetySpec;
-
-    protected final Context mContext;
-    protected final QSHost mHost;
-    protected final Handler mHandler;
-    protected final SecureSettings mSecureSettings;
-    protected final AutoAddTracker mAutoTracker;
-    private final HotspotController mHotspotController;
-    private final DataSaverController mDataSaverController;
-    private final ManagedProfileController mManagedProfileController;
-    private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
-    private NightDisplayListener mNightDisplayListener;
-    private final CastController mCastController;
-    private final DeviceControlsController mDeviceControlsController;
-    private final WalletController mWalletController;
-    private final ReduceBrightColorsController mReduceBrightColorsController;
-    private final SafetyController mSafetyController;
-    private final boolean mIsReduceBrightColorsAvailable;
-    private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
-
-    public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
-            QSHost host,
-            @Background Handler handler,
-            SecureSettings secureSettings,
-            HotspotController hotspotController,
-            DataSaverController dataSaverController,
-            ManagedProfileController managedProfileController,
-            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
-            CastController castController,
-            ReduceBrightColorsController reduceBrightColorsController,
-            DeviceControlsController deviceControlsController,
-            WalletController walletController,
-            SafetyController safetyController,
-            @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        mContext = context;
-        mHost = host;
-        mSecureSettings = secureSettings;
-        mCurrentUser = mHost.getUserContext().getUser();
-        mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
-        mHandler = handler;
-        mHotspotController = hotspotController;
-        mDataSaverController = dataSaverController;
-        mManagedProfileController = managedProfileController;
-        mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
-        mCastController = castController;
-        mReduceBrightColorsController = reduceBrightColorsController;
-        mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
-        mDeviceControlsController = deviceControlsController;
-        mWalletController = walletController;
-        mSafetyController = safetyController;
-        String safetySpecClass;
-        try {
-            safetySpecClass =
-                    context.getResources().getString(R.string.safety_quick_settings_tile_class);
-            if (safetySpecClass.length() == 0) {
-                safetySpecClass = null;
-            }
-        } catch (Resources.NotFoundException | NullPointerException e) {
-            safetySpecClass = null;
-        }
-        mSafetySpec = safetySpecClass != null ? CustomTile.toSpec(new ComponentName(mContext
-                .getPackageManager().getPermissionControllerPackageName(), safetySpecClass)) : null;
-    }
-
-    /**
-     * Init method must be called after construction to start listening
-     */
-    public void init() {
-        QSPipelineFlagsRepository.Utils.assertInLegacyMode();
-        if (mInitialized) {
-            Log.w(TAG, "Trying to re-initialize");
-            return;
-        }
-        mAutoTracker.initialize();
-        populateSettingsList();
-        startControllersAndSettingsListeners();
-        mInitialized = true;
-    }
-
-    protected void startControllersAndSettingsListeners() {
-        if (!mAutoTracker.isAdded(HOTSPOT)) {
-            mHotspotController.addCallback(mHotspotCallback);
-        }
-        if (!mAutoTracker.isAdded(SAVER)) {
-            mDataSaverController.addCallback(mDataSaverListener);
-        }
-        mManagedProfileController.addCallback(mProfileCallback);
-
-        mNightDisplayListener = mNightDisplayListenerBuilder
-                .setUser(mCurrentUser.getIdentifier())
-                .build();
-        if (!mAutoTracker.isAdded(NIGHT)
-                && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            mNightDisplayListener.setCallback(mNightDisplayCallback);
-        }
-        if (!mAutoTracker.isAdded(CAST)) {
-            mCastController.addCallback(mCastCallback);
-        }
-        if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
-            mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
-        }
-        // We always want this callback, because if the feature stops being supported,
-        // we want to remove the tile from AutoAddTracker. That way it will be re-added when the
-        // feature is reenabled (similar to work tile).
-        mDeviceControlsController.setCallback(mDeviceControlsCallback);
-        if (!mAutoTracker.isAdded(WALLET)) {
-            initWalletController();
-        }
-        if (mSafetySpec != null) {
-            if (!mAutoTracker.isAdded(mSafetySpec)) {
-                initSafetyTile();
-            }
-            mSafetyController.addCallback(mSafetyCallback);
-        }
-
-        int settingsN = mAutoAddSettingList.size();
-        for (int i = 0; i < settingsN; i++) {
-            if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
-                mAutoAddSettingList.get(i).setListening(true);
-            }
-        }
-    }
-
-    protected void stopListening() {
-        mHotspotController.removeCallback(mHotspotCallback);
-        mDataSaverController.removeCallback(mDataSaverListener);
-        mManagedProfileController.removeCallback(mProfileCallback);
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)
-                && mNightDisplayListener != null) {
-            mNightDisplayListener.setCallback(null);
-        }
-        if (mIsReduceBrightColorsAvailable) {
-            mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback);
-        }
-        mCastController.removeCallback(mCastCallback);
-        mDeviceControlsController.removeCallback();
-        if (mSafetySpec != null) {
-            mSafetyController.removeCallback(mSafetyCallback);
-        }
-        int settingsN = mAutoAddSettingList.size();
-        for (int i = 0; i < settingsN; i++) {
-            mAutoAddSettingList.get(i).setListening(false);
-        }
-    }
-
-    public void destroy() {
-        stopListening();
-        mAutoTracker.destroy();
-    }
-
-    /**
-     * Populates a list with the pairs setting:spec in the config resource.
-     * <p>
-     * This will only create {@link AutoAddSetting} objects for those tiles that have not been
-     * auto-added before, and set the corresponding {@link ContentObserver} to listening.
-     */
-    private void populateSettingsList() {
-        String [] autoAddList;
-        try {
-            autoAddList = mContext.getResources().getStringArray(
-                    R.array.config_quickSettingsAutoAdd);
-        } catch (Resources.NotFoundException e) {
-            Log.w(TAG, "Missing config resource");
-            return;
-        }
-        // getStringArray returns @NotNull, so if we got here, autoAddList is not null
-        for (String tile : autoAddList) {
-            String[] split = tile.split(SETTING_SEPARATOR);
-            if (split.length == 2) {
-                String setting = split[0];
-                String spec = split[1];
-                // Populate all the settings. As they may not have been added in other users
-                AutoAddSetting s = new AutoAddSetting(
-                        mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec);
-                mAutoAddSettingList.add(s);
-            } else {
-                Log.w(TAG, "Malformed item in array: " + tile);
-            }
-        }
-    }
-
-    /*
-     * This will be sent off the main thread if needed
-     */
-    @Override
-    public void changeUser(UserHandle newUser) {
-        if (!mInitialized) {
-            throw new IllegalStateException("AutoTileManager not initialized");
-        }
-        if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
-            mHandler.post(() -> changeUser(newUser));
-            return;
-        }
-        if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
-            return;
-        }
-        stopListening();
-        mCurrentUser = newUser;
-        int settingsN = mAutoAddSettingList.size();
-        for (int i = 0; i < settingsN; i++) {
-            mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
-        }
-        mAutoTracker.changeUser(newUser);
-        startControllersAndSettingsListeners();
-    }
-
-    @Override
-    public int getCurrentUserId() {
-        return mCurrentUser.getIdentifier();
-    }
-
-    private final ManagedProfileController.Callback mProfileCallback =
-            new ManagedProfileController.Callback() {
-                @Override
-                public void onManagedProfileChanged() {
-                    if (mManagedProfileController.hasActiveProfile()) {
-                        if (mAutoTracker.isAdded(WORK)) return;
-                        final int position = mAutoTracker.getRestoredTilePosition(WORK);
-                        mHost.addTile(WORK, position);
-                        mAutoTracker.setTileAdded(WORK);
-                    } else {
-                        if (!mAutoTracker.isAdded(WORK)) return;
-                        mHost.removeTile(WORK);
-                        mAutoTracker.setTileRemoved(WORK);
-                    }
-                }
-
-                @Override
-                public void onManagedProfileRemoved() {
-                }
-            };
-
-    private final DataSaverController.Listener mDataSaverListener = new Listener() {
-        @Override
-        public void onDataSaverChanged(boolean isDataSaving) {
-            if (mAutoTracker.isAdded(SAVER)) return;
-            if (isDataSaving) {
-                mHost.addTile(SAVER);
-                mAutoTracker.setTileAdded(SAVER);
-                mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener));
-            }
-        }
-    };
-
-    private final HotspotController.Callback mHotspotCallback = new Callback() {
-        @Override
-        public void onHotspotChanged(boolean enabled, int numDevices) {
-            if (mAutoTracker.isAdded(HOTSPOT)) return;
-            if (enabled) {
-                mHost.addTile(HOTSPOT);
-                mAutoTracker.setTileAdded(HOTSPOT);
-                mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback));
-            }
-        }
-    };
-
-    private final DeviceControlsController.Callback mDeviceControlsCallback =
-            new DeviceControlsController.Callback() {
-        @Override
-        public void onControlsUpdate(@Nullable Integer position) {
-            if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
-            if (position != null && !hasTile(DEVICE_CONTROLS)) {
-                mHost.addTile(DEVICE_CONTROLS, position);
-                mAutoTracker.setTileAdded(DEVICE_CONTROLS);
-            }
-            mHandler.post(() -> mDeviceControlsController.removeCallback());
-        }
-
-        @Override
-        public void removeControlsAutoTracker() {
-            mAutoTracker.setTileRemoved(DEVICE_CONTROLS);
-        }
-    };
-
-    private boolean hasTile(String tileSpec) {
-        if (tileSpec == null) return false;
-        Collection<QSTile> tiles = mHost.getTiles();
-        for (QSTile tile : tiles) {
-            if (tileSpec.equals(tile.getTileSpec())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void initWalletController() {
-        if (mAutoTracker.isAdded(WALLET)) return;
-        Integer position = mWalletController.getWalletPosition();
-
-        if (position != null) {
-            mHost.addTile(WALLET, position);
-            mAutoTracker.setTileAdded(WALLET);
-        }
-    }
-
-    private void initSafetyTile() {
-        if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
-            return;
-        }
-        mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
-        mAutoTracker.setTileAdded(mSafetySpec);
-    }
-
-    @VisibleForTesting
-    final NightDisplayListener.Callback mNightDisplayCallback =
-            new NightDisplayListener.Callback() {
-        @Override
-        public void onActivated(boolean activated) {
-            if (activated) {
-                addNightTile();
-            }
-        }
-
-        @Override
-        public void onAutoModeChanged(int autoMode) {
-            if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
-                    || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
-                addNightTile();
-            }
-        }
-
-        private void addNightTile() {
-            if (mAutoTracker.isAdded(NIGHT)) return;
-            mHost.addTile(NIGHT);
-            mAutoTracker.setTileAdded(NIGHT);
-            mHandler.post(() -> mNightDisplayListener.setCallback(null));
-        }
-    };
-
-    @VisibleForTesting
-    final ReduceBrightColorsController.Listener mReduceBrightColorsCallback =
-            new ReduceBrightColorsController.Listener() {
-                @Override
-                public void onActivated(boolean activated) {
-                    if (activated) {
-                        addReduceBrightColorsTile();
-                    }
-                }
-
-                @Override
-                public void onFeatureEnabledChanged(boolean enabled) {
-                    if (!enabled) {
-                        mHost.removeTile(BRIGHTNESS);
-                        mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
-                    }
-                }
-
-                private void addReduceBrightColorsTile() {
-                    if (mAutoTracker.isAdded(BRIGHTNESS)) return;
-                    mHost.addTile(BRIGHTNESS);
-                    mAutoTracker.setTileAdded(BRIGHTNESS);
-                    mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
-                }
-            };
-
-    @VisibleForTesting
-    final CastController.Callback mCastCallback = new CastController.Callback() {
-        @Override
-        public void onCastDevicesChanged() {
-            if (mAutoTracker.isAdded(CAST)) return;
-
-            boolean isCasting = false;
-            for (CastDevice device : mCastController.getCastDevices()) {
-                if (device.isCasting()) {
-                    isCasting = true;
-                    break;
-                }
-            }
-
-            if (isCasting) {
-                mHost.addTile(CAST);
-                mAutoTracker.setTileAdded(CAST);
-                mHandler.post(() -> mCastController.removeCallback(mCastCallback));
-            }
-        }
-    };
-
-    @VisibleForTesting
-    final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() {
-        @Override
-        public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) {
-            if (mSafetySpec == null) {
-                return;
-            }
-
-            if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
-                initSafetyTile();
-            } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
-                mHost.removeTile(mSafetySpec);
-                mAutoTracker.setTileRemoved(mSafetySpec);
-            }
-        }
-    };
-
-    @VisibleForTesting
-    protected UserSettingObserver getSecureSettingForKey(String key) {
-        for (UserSettingObserver s : mAutoAddSettingList) {
-            if (Objects.equals(key, s.getKey())) {
-                return s;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Tracks tiles that should be auto added when a setting changes.
-     * <p>
-     * When the setting changes to a value different from 0, if the tile has not been auto added
-     * before, it will be added and the listener will be stopped.
-     */
-    private class AutoAddSetting extends UserSettingObserver {
-        private final String mSpec;
-
-        AutoAddSetting(
-                SecureSettings secureSettings,
-                Handler handler,
-                String setting,
-                int userId,
-                String tileSpec
-        ) {
-            super(secureSettings, handler, setting, userId);
-            mSpec = tileSpec;
-        }
-
-        @Override
-        protected void handleValueChanged(int value, boolean observedChange) {
-            if (mAutoTracker.isAdded(mSpec)) {
-                // This should not be listening anymore
-                mHandler.post(() -> setListening(false));
-                return;
-            }
-            if (value != 0) {
-                if (mSpec.startsWith(CustomTile.PREFIX)) {
-                    mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true);
-                } else {
-                    mHost.addTile(mSpec);
-                }
-                mAutoTracker.setTileAdded(mSpec);
-                mHandler.post(() -> setListening(false));
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f178708..04604e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-
-
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -38,11 +36,8 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
@@ -55,8 +50,6 @@
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
     private final StatusBarWindowController mStatusBarWindowController;
 
-    private DarkReceiver mBattery;
-    private Clock mClock;
     private int mRotationOrientation = -1;
     @Nullable
     private View mCutoutSpace;
@@ -93,8 +86,6 @@
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
-        mBattery = findViewById(R.id.battery);
-        mClock = findViewById(R.id.clock);
         mCutoutSpace = findViewById(R.id.cutout_space_view);
 
         updateResources();
@@ -103,9 +94,6 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        // Always have Battery meters in the status bar observe the dark/light modes.
-        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
-        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock);
         if (updateDisplayParameters()) {
             updateLayoutForCutout();
             updateWindowHeight();
@@ -115,8 +103,6 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);
-        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mClock);
         mDisplayCutout = null;
     }
 
@@ -136,10 +122,6 @@
         updateWindowHeight();
     }
 
-    void onDensityOrFontScaleChanged() {
-        mClock.onDensityOrFontScaleChanged();
-    }
-
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         if (updateDisplayParameters()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a818c05..468a3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,10 +23,13 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.Flags
 import com.android.systemui.Gefingerpoken
+import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS
+import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
@@ -35,6 +38,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -68,19 +72,27 @@
     private val viewUtil: ViewUtil,
     private val configurationController: ConfigurationController,
     private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
+    private val darkIconDispatcher: DarkIconDispatcher,
 ) : ViewController<PhoneStatusBarView>(view) {
 
+    private lateinit var battery: BatteryMeterView
+    private lateinit var clock: Clock
     private lateinit var statusContainer: View
 
     private val configurationListener =
         object : ConfigurationController.ConfigurationListener {
             override fun onDensityOrFontScaleChanged() {
-                mView.onDensityOrFontScaleChanged()
+                clock.onDensityOrFontScaleChanged()
             }
         }
 
     override fun onViewAttached() {
         statusContainer = mView.requireViewById(R.id.system_icons)
+        clock = mView.requireViewById(R.id.clock)
+        battery = mView.requireViewById(R.id.battery)
+
+        addDarkReceivers()
+
         statusContainer.setOnHoverListener(
             statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer)
         )
@@ -133,7 +145,9 @@
         }
     }
 
-    override fun onViewDetached() {
+    @VisibleForTesting
+    public override fun onViewDetached() {
+        removeDarkReceivers()
         statusContainer.setOnHoverListener(null)
         progressProvider?.setReadyToHandleTransition(false)
         moveFromCenterAnimationController?.onViewDetached()
@@ -182,6 +196,16 @@
         }
     }
 
+    private fun addDarkReceivers() {
+        darkIconDispatcher.addDarkReceiver(battery)
+        darkIconDispatcher.addDarkReceiver(clock)
+    }
+
+    private fun removeDarkReceivers() {
+        darkIconDispatcher.removeDarkReceiver(battery)
+        darkIconDispatcher.removeDarkReceiver(clock)
+    }
+
     inner class PhoneStatusBarViewTouchHandler : Gefingerpoken {
         override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
             return if (Flags.statusBarSwipeOverChip()) {
@@ -285,6 +309,7 @@
         private val viewUtil: ViewUtil,
         private val configurationController: ConfigurationController,
         private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
+        private val darkIconDispatcher: DarkIconDispatcher,
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
@@ -309,6 +334,7 @@
                 viewUtil,
                 configurationController,
                 statusOverlayHoverListenerFactory,
+                darkIconDispatcher,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 775f34d..d281920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -174,15 +174,24 @@
         IndentingPrintWriter ipw = asIndenting(pw);
         ipw.println("BatteryController state:");
         ipw.increaseIndent();
-        ipw.print("mHasReceivedBattery="); ipw.println(mHasReceivedBattery);
-        ipw.print("mLevel="); ipw.println(mLevel);
-        ipw.print("mPluggedIn="); ipw.println(mPluggedIn);
-        ipw.print("mCharging="); ipw.println(mCharging);
-        ipw.print("mCharged="); ipw.println(mCharged);
-        ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
-        ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging);
-        ipw.print("mPowerSave="); ipw.println(mPowerSave);
-        ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
+        ipw.print("mHasReceivedBattery=");
+        ipw.println(mHasReceivedBattery);
+        ipw.print("mLevel=");
+        ipw.println(mLevel);
+        ipw.print("mPluggedIn=");
+        ipw.println(mPluggedIn);
+        ipw.print("mCharging=");
+        ipw.println(mCharging);
+        ipw.print("mCharged=");
+        ipw.println(mCharged);
+        ipw.print("mIsBatteryDefender=");
+        ipw.println(mIsBatteryDefender);
+        ipw.print("mIsIncompatibleCharging=");
+        ipw.println(mIsIncompatibleCharging);
+        ipw.print("mPowerSave=");
+        ipw.println(mPowerSave);
+        ipw.print("mStateUnknown=");
+        ipw.println(mStateUnknown);
         ipw.println("Callbacks:------------------");
         // Since the above lines are already indented, we need to indent twice for the callbacks.
         ipw.increaseIndent();
@@ -272,7 +281,7 @@
             }
 
             int chargingStatus = intent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
-            boolean isBatteryDefender = chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+            boolean isBatteryDefender = isBatteryDefenderMode(chargingStatus);
             if (isBatteryDefender != mIsBatteryDefender) {
                 mIsBatteryDefender = isBatteryDefender;
                 fireIsBatteryDefenderChanged();
@@ -359,11 +368,24 @@
         return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
     }
 
-    public boolean isBatteryDefender() {
+    /**
+     * This method is used for tests only. Returns whether the device is in battery defender
+     * mode.
+     */
+    @VisibleForTesting
+    protected boolean isBatteryDefender() {
         return mIsBatteryDefender;
     }
 
     /**
+     * Checks whether the device is in battery defender mode based on the current charging
+     * status. This method can be overridden to have a different definition for its subclasses.
+     */
+    protected boolean isBatteryDefenderMode(int chargingStatus) {
+        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+    }
+
+    /**
      * Returns whether the charging adapter is incompatible.
      */
     public boolean isIncompatibleCharging() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 1224275..e29e069 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -21,13 +21,12 @@
 import android.content.SharedPreferences
 import android.provider.Settings
 import android.util.Log
-import com.android.systemui.res.R
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
-import com.android.systemui.statusbar.phone.AutoTileManager
 import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -35,14 +34,16 @@
 /**
  * Watches for Device Controls QS Tile activation, which can happen in two ways:
  * <ol>
- *   <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high
- *       priority position.
- *   <li>Device controls service becomes available - For non-migrated users, create a tile and
- *       place at the end of active tiles, and initiate seeding where possible.
+ * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high priority
+ *   position.
+ * <li>Device controls service becomes available - For non-migrated users, create a tile and place
+ *   at the end of active tiles, and initiate seeding where possible.
  * </ol>
  */
 @SysUISingleton
-public class DeviceControlsControllerImpl @Inject constructor(
+public class DeviceControlsControllerImpl
+@Inject
+constructor(
     private val context: Context,
     private val controlsComponent: ControlsComponent,
     private val userContextProvider: UserContextProvider,
@@ -52,13 +53,14 @@
     private var callback: Callback? = null
     internal var position: Int? = null
 
-    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
-            if (!serviceInfos.isEmpty()) {
-                seedFavorites(serviceInfos)
+    private val listingCallback =
+        object : ControlsListingController.ControlsListingCallback {
+            override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+                if (!serviceInfos.isEmpty()) {
+                    seedFavorites(serviceInfos)
+                }
             }
         }
-    }
 
     companion object {
         private const val TAG = "DeviceControlsControllerImpl"
@@ -80,7 +82,7 @@
     }
 
     /**
-     * This migration logic assumes that something like [AutoTileManager] is tracking state
+     * This migration logic assumes that something like [AutoAddTracker] is tracking state
      * externally, and won't call this method after receiving a response via
      * [Callback#onControlsUpdate], once per user. Otherwise the calculated position may be
      * incorrect.
@@ -118,16 +120,19 @@
     }
 
     /**
-     * See if any available control service providers match one of the preferred components. If
-     * they do, and there are no current favorites for that component, query the preferred
-     * component for a limited number of suggested controls.
+     * See if any available control service providers match one of the preferred components. If they
+     * do, and there are no current favorites for that component, query the preferred component for
+     * a limited number of suggested controls.
      */
     private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) {
-        val preferredControlsPackages = context.getResources().getStringArray(
-            R.array.config_controlsPreferredPackages)
+        val preferredControlsPackages =
+            context.getResources().getStringArray(R.array.config_controlsPreferredPackages)
 
-        val prefs = userContextProvider.userContext.getSharedPreferences(
-            PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+        val prefs =
+            userContextProvider.userContext.getSharedPreferences(
+                PREFS_CONTROLS_FILE,
+                Context.MODE_PRIVATE
+            )
         val seededPackages =
             prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
 
@@ -157,21 +162,22 @@
         if (componentsToSeed.isEmpty()) return
 
         controlsController.seedFavoritesForComponents(
-                componentsToSeed,
-                { response ->
-                    Log.d(TAG, "Controls seeded: $response")
-                    if (response.accepted) {
-                        addPackageToSeededSet(prefs, response.packageName)
-                        if (position == null) {
-                            position = QS_DEFAULT_POSITION
-                        }
-                        fireControlsUpdate()
-
-                        controlsComponent.getControlsListingController().ifPresent {
-                            it.removeCallback(listingCallback)
-                        }
+            componentsToSeed,
+            { response ->
+                Log.d(TAG, "Controls seeded: $response")
+                if (response.accepted) {
+                    addPackageToSeededSet(prefs, response.packageName)
+                    if (position == null) {
+                        position = QS_DEFAULT_POSITION
                     }
-                })
+                    fireControlsUpdate()
+
+                    controlsComponent.getControlsListingController().ifPresent {
+                        it.removeCallback(listingCallback)
+                    }
+                }
+            }
+        )
     }
 
     private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index a4936e6..8e215f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -33,9 +33,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModelFactory
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.testKosmos
@@ -81,7 +82,8 @@
     private fun BouncerContentUnderTest() {
         PlatformTheme {
             BouncerContent(
-                viewModel = kosmos.bouncerViewModel,
+                viewModel =
+                    rememberViewModel { kosmos.bouncerSceneContentViewModelFactory.create() },
                 layout = BouncerSceneLayout.BESIDE_USER_SWITCHER,
                 modifier = Modifier.fillMaxSize().testTag("BouncerContent"),
                 dialogFactory = bouncerDialogFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 2948c02..4b61a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -24,14 +24,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.takeWhile
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,15 +51,15 @@
 
     @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos)
 
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val viewModel by lazy {
-        PatternBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = kosmos.testScope.backgroundScope,
-            interactor = bouncerInteractor,
+    private val viewModel =
+        kosmos.patternBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
             onIntentionalUserInput = {},
         )
+
+    @Before
+    fun setUp() {
+        viewModel.activateIn(motionTestRule.toolkit.testScope)
     }
 
     @Composable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
new file mode 100644
index 0000000..7583399
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.inputdevice.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import java.time.Instant
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialSchedulerRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: TutorialSchedulerRepository
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
+    @Before
+    fun setup() {
+        underTest =
+            TutorialSchedulerRepository(
+                context,
+                testScope.backgroundScope,
+                "TutorialSchedulerRepositoryTest"
+            )
+    }
+
+    @After
+    fun clear() {
+        testScope.launch { underTest.clearDataStore() }
+    }
+
+    @Test
+    fun initialState() =
+        testScope.runTest {
+            assertThat(underTest.wasEverConnected(KEYBOARD)).isFalse()
+            assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
+            assertThat(underTest.isLaunched(KEYBOARD)).isFalse()
+            assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
+        }
+
+    @Test
+    fun connectKeyboard() =
+        testScope.runTest {
+            val now = Instant.now().toEpochMilli()
+            underTest.updateConnectTime(KEYBOARD, now)
+
+            assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue()
+            assertThat(underTest.connectTime(KEYBOARD)).isEqualTo(now)
+            assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
+        }
+
+    @Test
+    fun launchKeyboard() =
+        testScope.runTest {
+            underTest.updateLaunch(KEYBOARD)
+
+            assertThat(underTest.isLaunched(KEYBOARD)).isTrue()
+            assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
deleted file mode 100644
index 1eeaef7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-
-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.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-@SmallTest
-public class AutoAddTrackerTest extends SysuiTestCase {
-
-    private static final int END_POSITION = -1;
-    private static final int USER = 0;
-
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private QSHost mQSHost;
-    @Mock
-    private DumpManager mDumpManager;
-    @Captor
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-    @Captor
-    private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
-
-    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
-    private AutoAddTracker mAutoTracker;
-    private SecureSettings mSecureSettings;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mSecureSettings = new FakeSettings();
-
-        mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
-
-        mAutoTracker = createAutoAddTracker(USER);
-        mAutoTracker.initialize();
-    }
-
-    @Test
-    public void testChangeFromBackup() {
-        assertFalse(mAutoTracker.isAdded(SAVER));
-
-        mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-
-        mAutoTracker.destroy();
-    }
-
-    @Test
-    public void testSetAdded() {
-        assertFalse(mAutoTracker.isAdded(SAVER));
-        mAutoTracker.setTileAdded(SAVER);
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-
-        mAutoTracker.destroy();
-    }
-
-    @Test
-    public void testPersist() {
-        assertFalse(mAutoTracker.isAdded(SAVER));
-        mAutoTracker.setTileAdded(SAVER);
-
-        mAutoTracker.destroy();
-        mAutoTracker = createAutoAddTracker(USER);
-        mAutoTracker.initialize();
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-
-        mAutoTracker.destroy();
-    }
-
-    @Test
-    public void testIndependentUsers() {
-        mAutoTracker.setTileAdded(SAVER);
-
-        mAutoTracker = createAutoAddTracker(USER + 1);
-        mAutoTracker.initialize();
-        assertFalse(mAutoTracker.isAdded(SAVER));
-    }
-
-    @Test
-    public void testChangeUser() {
-        mAutoTracker.setTileAdded(SAVER);
-
-        mAutoTracker = createAutoAddTracker(USER + 1);
-        mAutoTracker.changeUser(UserHandle.of(USER));
-        assertTrue(mAutoTracker.isAdded(SAVER));
-    }
-
-    @Test
-    public void testRestoredTilePositionPreserved() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-        String restoredTiles = "saver,internet,work,cast";
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        assertEquals(2, mAutoTracker.getRestoredTilePosition("work"));
-    }
-
-    @Test
-    public void testNoRestoredTileReturnsEndPosition() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null);
-
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work"));
-    }
-
-    @Test
-    public void testBroadcastReceiverRegistered() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
-                anyInt(), any());
-
-        assertTrue(
-                mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
-    }
-
-    @Test
-    public void testBroadcastReceiverChangesWithUser() {
-        mAutoTracker.changeUser(UserHandle.of(USER + 1));
-
-        InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
-        inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
-        inOrder.verify(mBroadcastDispatcher)
-                .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt(),
-                        any());
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,work,internet,cast";
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, don't remove any current tiles
-        verify(mQSHost, never()).removeTiles(any());
-        assertEquals(restoredAutoAddTiles,
-                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
-        verify(mBroadcastDispatcher)
-                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,internet,cast";
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, remove work tile
-        verify(mQSHost).removeTiles(List.of("work"));
-        assertEquals(restoredAutoAddTiles,
-                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
-        verify(mBroadcastDispatcher)
-                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,internet,cast";
-        Intent restoreTilesIntent =
-                makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, remove work tile
-        verify(mQSHost).removeTiles(List.of("work"));
-        assertEquals(restoredAutoAddTiles,
-                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
-        verify(mBroadcastDispatcher)
-                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,internet,cast";
-        Intent restoreTilesIntent =
-                makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, remove work tile
-        verify(mQSHost).removeTiles(List.of("work"));
-
-        String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
-        assertEquals(2, setting.split(",").length);
-        assertTrue(setting.contains("work"));
-        assertTrue(setting.contains("inversion"));
-    }
-
-
-    private Intent makeRestoreIntent(
-            String settingName, String previousValue, String restoredValue) {
-        Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
-        intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
-        intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
-        intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
-        return intent;
-    }
-
-    private AutoAddTracker createAutoAddTracker(int user) {
-        // Null handler wil dispatch sync.
-        return new AutoAddTracker(
-                mSecureSettings,
-                mBroadcastDispatcher,
-                mQSHost,
-                mDumpManager,
-                null,
-                mBackgroundExecutor,
-                user
-        );
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
deleted file mode 100644
index 6d1bc82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ /dev/null
@@ -1,786 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-
-import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
-import static com.android.systemui.Flags.FLAG_QS_NEW_TILES;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.CollectionUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.di.NewQSTileFactory;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.FakeSharedPreferences;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import dagger.Lazy;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class QSTileHostTest extends SysuiTestCase {
-
-    private static String MOCK_STATE_STRING = "MockState";
-    private static ComponentName CUSTOM_TILE =
-            ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
-    private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
-    private static final String SETTING = QSHost.TILES_SETTING;
-    @Mock
-    private PluginManager mPluginManager;
-    @Mock
-    private TunerService mTunerService;
-    @Mock
-    private AutoTileManager mAutoTiles;
-    @Mock
-    private ShadeController mShadeController;
-    @Mock
-    private QSLogger mQSLogger;
-    @Mock
-    private CustomTile mCustomTile;
-    @Mock
-    private UserTracker mUserTracker;
-    @Mock
-    private CustomTileStatePersister mCustomTileStatePersister;
-    @Mock
-    private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
-    @Mock
-    private TileLifecycleManager mTileLifecycleManager;
-    @Mock
-    private UserFileManager mUserFileManager;
-
-    private SecureSettings mSecureSettings;
-
-    private QSFactory mDefaultFactory;
-
-    private SparseArray<SharedPreferences> mSharedPreferencesByUser;
-
-    private QSPipelineFlagsRepository mQSPipelineFlagsRepository;
-
-    private FakeExecutor mMainExecutor;
-
-    private QSTileHost mQSTileHost;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
-        mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES);
-        mQSPipelineFlagsRepository = new QSPipelineFlagsRepository();
-
-        mMainExecutor = new FakeExecutor(new FakeSystemClock());
-
-        mSharedPreferencesByUser = new SparseArray<>();
-        when(mTileLifecycleManagerFactory
-                .create(any(Intent.class), any(UserHandle.class)))
-                .thenReturn(mTileLifecycleManager);
-        when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
-                .thenAnswer((Answer<SharedPreferences>) invocation -> {
-                    assertEquals(QSTileHost.TILES, invocation.getArgument(0));
-                    int userId = invocation.getArgument(2);
-                    if (!mSharedPreferencesByUser.contains(userId)) {
-                        mSharedPreferencesByUser.put(userId, new FakeSharedPreferences());
-                    }
-                    return mSharedPreferencesByUser.get(userId);
-                });
-
-        mSecureSettings = new FakeSettings();
-        saveSetting("");
-        setUpTileFactory();
-        mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
-                mPluginManager, mTunerService, () -> mAutoTiles, () -> mShadeController,
-                mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
-                mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
-        mMainExecutor.runAllReady();
-
-        mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                super.onChange(selfChange);
-                mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting()));
-                mMainExecutor.runAllReady();
-            }
-        }, mUserTracker.getUserId());
-    }
-
-    private void saveSetting(String value) {
-        mSecureSettings.putStringForUser(
-                SETTING, value, "", false, mUserTracker.getUserId(), false);
-    }
-
-    private String getSetting() {
-        return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId());
-    }
-
-    private void setUpTileFactory() {
-        mDefaultFactory = new FakeQSFactory(spec -> {
-            if ("spec1".equals(spec)) {
-                return new TestTile1(mQSTileHost);
-            } else if ("spec2".equals(spec)) {
-                return new TestTile2(mQSTileHost);
-            } else if ("spec3".equals(spec)) {
-                return new TestTile3(mQSTileHost);
-            } else if ("na".equals(spec)) {
-                return new NotAvailableTile(mQSTileHost);
-            } else if (CUSTOM_TILE_SPEC.equals(spec)) {
-                QSTile tile = mCustomTile;
-                QSTile.State s = mock(QSTile.State.class);
-                s.spec = spec;
-                when(mCustomTile.getState()).thenReturn(s);
-                return tile;
-            } else if ("internet".equals(spec)
-                    || "wifi".equals(spec)
-                    || "cell".equals(spec)) {
-                return new TestTile1(mQSTileHost);
-            } else {
-                return null;
-            }
-        });
-        when(mCustomTile.isAvailable()).thenReturn(true);
-    }
-
-    @Test
-    public void testLoadTileSpecs_emptySetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, "");
-        assertFalse(tiles.isEmpty());
-    }
-
-    @Test
-    public void testLoadTileSpecs_nullSetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, null);
-        assertFalse(tiles.isEmpty());
-    }
-
-    @Test
-    public void testInvalidSpecUsesDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
-        saveSetting("not-valid");
-
-        assertEquals(2, mQSTileHost.getTiles().size());
-    }
-
-    @Test
-    public void testRemoveWifiAndCellularWithoutInternet() {
-        saveSetting("wifi, spec1, cell, spec2");
-
-        assertEquals("internet", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec1", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testRemoveWifiAndCellularWithInternet() {
-        saveSetting("wifi, spec1, cell, spec2, internet");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-        assertEquals("internet", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testRemoveWifiWithoutInternet() {
-        saveSetting("spec1, wifi, spec2");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("internet", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testRemoveCellWithInternet() {
-        saveSetting("spec1, spec2, cell, internet");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-        assertEquals("internet", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testNoWifiNoCellularNoInternet() {
-        saveSetting("spec1,spec2");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-    }
-
-    @Test
-    public void testSpecWithInvalidDoesNotUseDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
-        saveSetting("spec2,not-valid");
-
-        assertEquals(1, mQSTileHost.getTiles().size());
-        QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
-        assertTrue(element instanceof TestTile2);
-    }
-
-    @Test
-    public void testDump() {
-        saveSetting("spec1,spec2");
-        StringWriter w = new StringWriter();
-        PrintWriter pw = new PrintWriter(w);
-        mQSTileHost.dump(pw, new String[]{});
-
-        String output = "QSTileHost:" + "\n"
-                + "tile specs: [spec1, spec2]" + "\n"
-                + "current user: 0" + "\n"
-                + "is dirty: false" + "\n"
-                + "tiles:" + "\n"
-                + "TestTile1:" + "\n"
-                + "    MockState" + "\n"
-                + "TestTile2:" + "\n"
-                + "    MockState" + "\n";
-
-        System.out.println(output);
-        System.out.println(w.getBuffer().toString());
-
-        assertEquals(output, w.getBuffer().toString());
-    }
-
-    @Test
-    public void testDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles_default, "spec1");
-        saveSetting("default");
-        assertEquals(1, mQSTileHost.getTiles().size());
-        QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
-        assertTrue(element instanceof TestTile1);
-        verify(mQSLogger).logTileAdded("spec1");
-    }
-
-    @Test
-    public void testNoRepeatedSpecs_addTile() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
-        saveSetting("spec1,spec2");
-
-        mQSTileHost.addTile("spec1");
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-    }
-
-    @Test
-    public void testAddTileAtValidPosition() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
-        saveSetting("spec1,spec3");
-
-        mQSTileHost.addTile("spec2", 1);
-        mMainExecutor.runAllReady();
-
-        assertEquals(3, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec3", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testAddTileAtInvalidPositionAddsToEnd() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
-        saveSetting("spec1,spec3");
-
-        mQSTileHost.addTile("spec2", 100);
-        mMainExecutor.runAllReady();
-
-        assertEquals(3, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec3", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testAddTileAtEnd() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
-        saveSetting("spec1,spec3");
-
-        mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
-        mMainExecutor.runAllReady();
-
-        assertEquals(3, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec3", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testNoRepeatedSpecs_customTile() {
-        saveSetting(CUSTOM_TILE_SPEC);
-
-        mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
-        mMainExecutor.runAllReady();
-
-        assertEquals(1, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
-    }
-
-    @Test
-    public void testAddedAtBeginningOnDefault_customTile() {
-        saveSetting("spec1"); // seed
-
-        mQSTileHost.addTile(CUSTOM_TILE);
-        mMainExecutor.runAllReady();
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
-    }
-
-    @Test
-    public void testAddedAtBeginning_customTile() {
-        saveSetting("spec1"); // seed
-
-        mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
-        mMainExecutor.runAllReady();
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
-    }
-
-    @Test
-    public void testAddedAtEnd_customTile() {
-        saveSetting("spec1"); // seed
-
-        mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
-        mMainExecutor.runAllReady();
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(1));
-    }
-
-    @Test
-    public void testLoadTileSpec_repeated() {
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
-
-        assertEquals(2, specs.size());
-        assertEquals("spec1", specs.get(0));
-        assertEquals("spec2", specs.get(1));
-    }
-
-    @Test
-    public void testLoadTileSpec_repeatedInDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
-    }
-
-    @Test
-    public void testLoadTileSpec_repeatedDefaultAndSetting() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles_default, "spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
-    }
-
-    @Test
-    public void testNotAvailableTile_specNotNull() {
-        saveSetting("na");
-        verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
-    }
-
-    @Test
-    public void testCustomTileRemoved_stateDeleted() {
-        mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of());
-
-        verify(mCustomTileStatePersister)
-                .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
-    }
-
-    @Test
-    public void testRemoveTiles() {
-        saveSetting("spec1,spec2,spec3");
-
-        mQSTileHost.removeTiles(List.of("spec1", "spec2"));
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec3"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testTilesRemovedInQuickSuccession() {
-        saveSetting("spec1,spec2,spec3");
-        mQSTileHost.removeTile("spec1");
-        mQSTileHost.removeTile("spec3");
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
-        assertEquals("spec2", getSetting());
-    }
-
-    @Test
-    public void testAddTileInMainThread() {
-        saveSetting("spec1,spec2");
-
-        mQSTileHost.addTile("spec3");
-        assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testRemoveTileInMainThread() {
-        saveSetting("spec1,spec2");
-
-        mQSTileHost.removeTile("spec1");
-        assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testRemoveTilesInMainThread() {
-        saveSetting("spec1,spec2,spec3");
-
-        mQSTileHost.removeTiles(List.of("spec3", "spec1"));
-        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testRemoveTileByUserInMainThread() {
-        saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
-        mQSTileHost.removeTileByUser(CUSTOM_TILE);
-        assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testNonValidTileNotStoredInSettings() {
-        saveSetting("spec1,not-valid");
-
-        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
-        assertEquals("spec1", getSetting());
-    }
-
-    @Test
-    public void testNotAvailableTileNotStoredInSettings() {
-        saveSetting("spec1,na");
-
-        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
-        assertEquals("spec1", getSetting());
-    }
-
-    @Test
-    public void testIsTileAdded_true() {
-        int user = mUserTracker.getUserId();
-        getSharedPreferencesForUser(user)
-                .edit()
-                .putBoolean(CUSTOM_TILE.flattenToString(), true)
-                .apply();
-
-        assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
-    }
-
-    @Test
-    public void testIsTileAdded_false() {
-        int user = mUserTracker.getUserId();
-        getSharedPreferencesForUser(user)
-                .edit()
-                .putBoolean(CUSTOM_TILE.flattenToString(), false)
-                .apply();
-
-        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
-    }
-
-    @Test
-    public void testIsTileAdded_notSet() {
-        int user = mUserTracker.getUserId();
-
-        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
-    }
-
-    @Test
-    public void testIsTileAdded_differentUser() {
-        int user = mUserTracker.getUserId();
-        mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user)
-                .edit()
-                .putBoolean(CUSTOM_TILE.flattenToString(), true)
-                .apply();
-
-        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1));
-    }
-
-    @Test
-    public void testSetTileAdded_true() {
-        int user = mUserTracker.getUserId();
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        assertTrue(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileAdded_false() {
-        int user = mUserTracker.getUserId();
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
-
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileAdded_differentUser() {
-        int user = mUserTracker.getUserId();
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        assertFalse(getSharedPreferencesForUser(user + 1)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileRemoved_afterCustomTileChangedByUser() {
-        int user = mUserTracker.getUserId();
-        saveSetting(CUSTOM_TILE_SPEC);
-
-        // This will be done by TileServiceManager
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        mQSTileHost.changeTilesByUser(mQSTileHost.getSpecs(), List.of("spec1"));
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileRemoved_removedByUser() {
-        int user = mUserTracker.getUserId();
-        saveSetting(CUSTOM_TILE_SPEC);
-
-        // This will be done by TileServiceManager
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        mQSTileHost.removeTileByUser(CUSTOM_TILE);
-        mMainExecutor.runAllReady();
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileRemoved_removedBySystem() {
-        int user = mUserTracker.getUserId();
-        saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
-        // This will be done by TileServiceManager
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
-        mMainExecutor.runAllReady();
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testProtoDump_noTiles() {
-        SystemUIProtoDump proto = new SystemUIProtoDump();
-        mQSTileHost.dumpProto(proto, new String[0]);
-
-        assertEquals(0, proto.tiles.length);
-    }
-
-    @Test
-    public void testTilesInOrder() {
-        saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
-        SystemUIProtoDump proto = new SystemUIProtoDump();
-        mQSTileHost.dumpProto(proto, new String[0]);
-
-        assertEquals(2, proto.tiles.length);
-        assertEquals("spec1", proto.tiles[0].getSpec());
-        assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName);
-        assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
-    }
-
-    private SharedPreferences getSharedPreferencesForUser(int user) {
-        return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
-    }
-
-    private class TestQSTileHost extends QSTileHost {
-        TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider,
-                QSFactory defaultFactory, Executor mainExecutor,
-                PluginManager pluginManager, TunerService tunerService,
-                Provider<AutoTileManager> autoTiles,
-                Lazy<ShadeController> shadeController, QSLogger qsLogger,
-                UserTracker userTracker, SecureSettings secureSettings,
-                CustomTileStatePersister customTileStatePersister,
-                TileLifecycleManager.Factory tileLifecycleManagerFactory,
-                UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
-            super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
-                    tunerService, autoTiles, shadeController, qsLogger,
-                    userTracker, secureSettings, customTileStatePersister,
-                    tileLifecycleManagerFactory, userFileManager, featureFlags);
-        }
-
-        @Override
-        public void onPluginConnected(QSFactory plugin, Context pluginContext) {
-        }
-
-        @Override
-        public void onPluginDisconnected(QSFactory plugin) {
-        }
-    }
-
-
-    private class TestTile extends QSTileImpl<QSTile.State> {
-
-        protected TestTile(QSHost host) {
-            super(
-                    host,
-                    mock(QsEventLogger.class),
-                    mock(Looper.class),
-                    mock(Handler.class),
-                    new FalsingManagerFake(),
-                    mock(MetricsLogger.class),
-                    mock(StatusBarStateController.class),
-                    mock(ActivityStarter.class),
-                    QSTileHostTest.this.mQSLogger
-            );
-        }
-
-        @Override
-        public State newTileState() {
-            State s = mock(QSTile.State.class);
-            when(s.toString()).thenReturn(MOCK_STATE_STRING);
-            return s;
-        }
-
-        @Override
-        protected void handleClick(@Nullable Expandable expandable) {}
-
-        @Override
-        protected void handleUpdateState(State state, Object arg) {}
-
-        @Override
-        public int getMetricsCategory() {
-            return 0;
-        }
-
-        @Override
-        public Intent getLongClickIntent() {
-            return null;
-        }
-
-        @Override
-        public CharSequence getTileLabel() {
-            return null;
-        }
-    }
-
-    private class TestTile1 extends TestTile {
-
-        protected TestTile1(QSHost host) {
-            super(host);
-        }
-    }
-
-    private class TestTile2 extends TestTile {
-
-        protected TestTile2(QSHost host) {
-            super(host);
-        }
-    }
-
-    private class TestTile3 extends TestTile {
-
-        protected TestTile3(QSHost host) {
-            super(host);
-        }
-    }
-
-    private class NotAvailableTile extends TestTile {
-
-        protected NotAvailableTile(QSHost host) {
-            super(host);
-        }
-
-        @Override
-        public boolean isAvailable() {
-            return false;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 970cd17..090a85b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -15,20 +15,6 @@
     private val underTest = QSPipelineFlagsRepository()
 
     @Test
-    fun pipelineFlagDisabled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
-        assertThat(underTest.pipelineEnabled).isFalse()
-    }
-
-    @Test
-    fun pipelineFlagEnabled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
-        assertThat(underTest.pipelineEnabled).isTrue()
-    }
-
-    @Test
     fun tilesFlagDisabled() {
         mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 2d11917..63192f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -36,6 +36,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.os.Handler;
+import android.platform.test.annotations.EnableFlags;
 import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -52,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -672,17 +674,31 @@
     }
 
     @Test
-    public void testForceResetSwipeStateDoesNothingIfTranslationIsZero() {
+    public void testForceResetSwipeStateDoesNothingIfTranslationIsZeroAndAlphaIsOne() {
         doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
         doReturn(0f).when(mNotificationRow).getTranslationX();
+        doReturn(1f).when(mNotificationRow).getAlpha();
 
         mSwipeHelper.forceResetSwipeState(mNotificationRow);
 
         verify(mNotificationRow).getTranslationX();
+        verify(mNotificationRow).getAlpha();
         verifyNoMoreInteractions(mNotificationRow);
     }
 
     @Test
+    @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
+    public void testForceResetSwipeStateResetsAlphaIfTranslationIsZeroAndAlphaNotOne() {
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+        doReturn(0f).when(mNotificationRow).getTranslationX();
+        doReturn(0.5f).when(mNotificationRow).getAlpha();
+
+        mSwipeHelper.forceResetSwipeState(mNotificationRow);
+
+        verify(mNotificationRow).setContentAlpha(eq(1f));
+    }
+
+    @Test
     public void testForceResetSwipeStateResetsTranslationAndAlpha() {
         doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
         doReturn(10f).when(mNotificationRow).getTranslationX();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
deleted file mode 100644
index 665544d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNotNull;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.NightDisplayListener;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.UserSettingObserver;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastDevice;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.inject.Named;
-
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-@SmallTest
-public class AutoTileManagerTest extends SysuiTestCase {
-
-    private static final String TEST_SETTING = "setting";
-    private static final String TEST_SPEC = "spec";
-    private static final String TEST_SETTING_COMPONENT = "setting_component";
-    private static final String TEST_COMPONENT = "test_pkg/test_cls";
-    private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
-    private static final String TEST_CUSTOM_SAFETY_CLASS = "safety_cls";
-    private static final String TEST_CUSTOM_SAFETY_PKG = "safety_pkg";
-    private static final String TEST_CUSTOM_SAFETY_SPEC = CustomTile.toSpec(new ComponentName(
-            TEST_CUSTOM_SAFETY_PKG, TEST_CUSTOM_SAFETY_CLASS));
-    private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
-
-    private static final int USER = 0;
-
-    @Mock private QSHost mQsHost;
-    @Mock private AutoAddTracker mAutoAddTracker;
-    @Mock private CastController mCastController;
-    @Mock private HotspotController mHotspotController;
-    @Mock private DataSaverController mDataSaverController;
-    @Mock private ManagedProfileController mManagedProfileController;
-    @Mock private NightDisplayListener mNightDisplayListener;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
-    @Mock private ReduceBrightColorsController mReduceBrightColorsController;
-    @Mock private DeviceControlsController mDeviceControlsController;
-    @Mock private WalletController mWalletController;
-    @Mock private SafetyController mSafetyController;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private AutoAddTracker.Builder mAutoAddTrackerBuilder;
-    @Mock private Context mUserContext;
-    @Spy private PackageManager mPackageManager;
-    private final boolean mIsReduceBrightColorsAvailable = true;
-
-    private AutoTileManager mAutoTileManager; // under test
-
-    private SecureSettings mSecureSettings;
-    private ManagedProfileController.Callback mManagedProfileCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mSecureSettings = new FakeSettings();
-
-        mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.array.config_quickSettingsAutoAdd,
-                new String[] {
-                        TEST_SETTING + SEPARATOR + TEST_SPEC,
-                        TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC
-                }
-        );
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_nightDisplayAvailable, true);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.string.safety_quick_settings_tile_class, TEST_CUSTOM_SAFETY_CLASS);
-
-        when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
-        when(mQsHost.getUserContext()).thenReturn(mUserContext);
-        when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
-        mPackageManager = Mockito.spy(mContext.getPackageManager());
-        when(mPackageManager.getPermissionControllerPackageName())
-                .thenReturn(TEST_CUSTOM_SAFETY_PKG);
-        Context context = Mockito.spy(mContext);
-        when(context.getPackageManager()).thenReturn(mPackageManager);
-        when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
-
-        mAutoTileManager = createAutoTileManager(context);
-        mAutoTileManager.init();
-    }
-
-    @After
-    public void tearDown() {
-        mAutoTileManager.destroy();
-    }
-
-    private AutoTileManager createAutoTileManager(
-            Context context,
-            AutoAddTracker.Builder autoAddTrackerBuilder,
-            HotspotController hotspotController,
-            DataSaverController dataSaverController,
-            ManagedProfileController managedProfileController,
-            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
-            CastController castController,
-            ReduceBrightColorsController reduceBrightColorsController,
-            DeviceControlsController deviceControlsController,
-            WalletController walletController,
-            SafetyController safetyController,
-            @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        return new AutoTileManager(context, autoAddTrackerBuilder, mQsHost,
-                Handler.createAsync(TestableLooper.get(this).getLooper()),
-                mSecureSettings,
-                hotspotController,
-                dataSaverController,
-                managedProfileController,
-                mNightDisplayListenerBuilder,
-                castController,
-                reduceBrightColorsController,
-                deviceControlsController,
-                walletController,
-                safetyController,
-                isReduceBrightColorsAvailable);
-    }
-
-    private AutoTileManager createAutoTileManager(Context context) {
-        return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
-                mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
-                mCastController, mReduceBrightColorsController, mDeviceControlsController,
-                mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
-    }
-
-    @Test
-    public void testCreatedAutoTileManagerIsNotInitialized() {
-        AutoAddTracker.Builder builder = mock(AutoAddTracker.Builder.class, Answers.RETURNS_SELF);
-        AutoAddTracker tracker = mock(AutoAddTracker.class);
-        when(builder.build()).thenReturn(tracker);
-        HotspotController hC = mock(HotspotController.class);
-        DataSaverController dSC = mock(DataSaverController.class);
-        ManagedProfileController mPC = mock(ManagedProfileController.class);
-        NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
-        CastController cC = mock(CastController.class);
-        ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
-        DeviceControlsController dCC = mock(DeviceControlsController.class);
-        WalletController wC = mock(WalletController.class);
-        SafetyController sC = mock(SafetyController.class);
-
-        AutoTileManager manager =
-                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
-                        dCC, wC, sC, true);
-
-        verify(tracker, never()).initialize();
-        verify(hC, never()).addCallback(any());
-        verify(dSC, never()).addCallback(any());
-        verify(mPC, never()).addCallback(any());
-        verifyNoMoreInteractions(nDSB);
-        verify(cC, never()).addCallback(any());
-        verify(rBC, never()).addCallback(any());
-        verify(dCC, never()).setCallback(any());
-        verify(wC, never()).getWalletPosition();
-        verify(sC, never()).addCallback(any());
-        assertNull(manager.getSecureSettingForKey(TEST_SETTING));
-        assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT));
-    }
-
-    @Test
-    public void testChangeUserWhenNotInitializedThrows() {
-        AutoTileManager manager = createAutoTileManager(mock(Context.class));
-
-        try {
-            manager.changeUser(UserHandle.of(USER + 1));
-            fail();
-        } catch (Exception e) {
-            // This should throw and take this path
-        }
-    }
-
-    @Test
-    public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
-        TestableLooper.get(this).runWithLooper(() ->
-                mAutoTileManager.changeUser(UserHandle.of(USER + 1))
-        );
-
-        InOrder inOrderHotspot = inOrder(mHotspotController);
-        inOrderHotspot.verify(mHotspotController).removeCallback(any());
-        inOrderHotspot.verify(mHotspotController).addCallback(any());
-
-        InOrder inOrderDataSaver = inOrder(mDataSaverController);
-        inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
-        inOrderDataSaver.verify(mDataSaverController).addCallback(any());
-
-        InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
-        inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
-        inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
-
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
-        }
-
-        InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
-
-        InOrder inOrderCast = inOrder(mCastController);
-        inOrderCast.verify(mCastController).removeCallback(any());
-        inOrderCast.verify(mCastController).addCallback(any());
-
-        InOrder inOrderDevices = inOrder(mDeviceControlsController);
-        inOrderDevices.verify(mDeviceControlsController).removeCallback();
-        inOrderDevices.verify(mDeviceControlsController).setCallback(any());
-
-        verify(mWalletController, times(2)).getWalletPosition();
-
-        InOrder inOrderSafety = inOrder(mSafetyController);
-        inOrderSafety.verify(mSafetyController).removeCallback(any());
-        inOrderSafety.verify(mSafetyController).addCallback(any());
-
-        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
-        assertEquals(USER + 1, setting.getCurrentUser());
-        assertTrue(setting.isListening());
-    }
-
-    @Test
-    public void testChangeUserSomeCallbacksNotAdded() throws Exception {
-        when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
-        when(mAutoAddTracker.isAdded("work")).thenReturn(true);
-        when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
-        when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
-
-        TestableLooper.get(this).runWithLooper(() ->
-                mAutoTileManager.changeUser(UserHandle.of(USER + 1))
-        );
-
-        verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
-
-        InOrder inOrderHotspot = inOrder(mHotspotController);
-        inOrderHotspot.verify(mHotspotController).removeCallback(any());
-        inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
-
-        InOrder inOrderDataSaver = inOrder(mDataSaverController);
-        inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
-        inOrderDataSaver.verify(mDataSaverController).addCallback(any());
-
-        InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
-        inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
-        inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
-
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
-        }
-
-        InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
-
-        InOrder inOrderCast = inOrder(mCastController);
-        inOrderCast.verify(mCastController).removeCallback(any());
-        inOrderCast.verify(mCastController, never()).addCallback(any());
-
-        InOrder inOrderDevices = inOrder(mDeviceControlsController);
-        inOrderDevices.verify(mDeviceControlsController).removeCallback();
-        inOrderDevices.verify(mDeviceControlsController).setCallback(any());
-
-        verify(mWalletController, times(2)).getWalletPosition();
-
-        InOrder inOrderSafety = inOrder(mSafetyController);
-        inOrderSafety.verify(mSafetyController).removeCallback(any());
-        inOrderSafety.verify(mSafetyController).addCallback(any());
-
-        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
-        assertEquals(USER + 1, setting.getCurrentUser());
-        assertFalse(setting.isListening());
-    }
-
-    @Test
-    public void testGetCurrentUserId() throws Exception {
-        assertEquals(USER, mAutoTileManager.getCurrentUserId());
-
-        TestableLooper.get(this).runWithLooper(() ->
-                mAutoTileManager.changeUser(UserHandle.of(USER + 100))
-        );
-
-        assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
-    }
-
-    @Test
-    public void nightTileAdded_whenActivated() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onActivated(true);
-        verify(mQsHost).addTile("night");
-    }
-
-    @Test
-    public void nightTileNotAdded_whenDeactivated() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onActivated(false);
-        verify(mQsHost, never()).addTile("night");
-    }
-
-    @Test
-    public void nightTileAdded_whenNightModeTwilight() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                ColorDisplayManager.AUTO_MODE_TWILIGHT);
-        verify(mQsHost).addTile("night");
-    }
-
-    @Test
-    public void nightTileAdded_whenNightModeCustom() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
-        verify(mQsHost).addTile("night");
-    }
-
-    @Test
-    public void nightTileNotAdded_whenNightModeDisabled() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                ColorDisplayManager.AUTO_MODE_DISABLED);
-        verify(mQsHost, never()).addTile("night");
-    }
-
-    @Test
-    public void reduceBrightColorsTileAdded_whenActivated() {
-        mAutoTileManager.mReduceBrightColorsCallback.onActivated(true);
-        verify(mQsHost).addTile("reduce_brightness");
-    }
-
-    @Test
-    public void reduceBrightColorsTileNotAdded_whenDeactivated() {
-        mAutoTileManager.mReduceBrightColorsCallback.onActivated(false);
-        verify(mQsHost, never()).addTile("reduce_brightness");
-    }
-
-    private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
-        CastDevice.CastState state = isCasting
-                ? CastDevice.CastState.Connected
-                : CastDevice.CastState.Disconnected;
-        return Collections.singletonList(
-                new CastDevice(
-                        "id",
-                        /* name= */ null,
-                        /* description= */ null,
-                        /* state= */ state,
-                        /* origin= */ CastDevice.CastOrigin.MediaProjection,
-                        /* tag= */ null));
-    }
-
-    @Test
-    public void castTileAdded_whenDeviceIsCasting() {
-        doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
-        mAutoTileManager.mCastCallback.onCastDevicesChanged();
-        verify(mQsHost).addTile("cast");
-    }
-
-    @Test
-    public void castTileNotAdded_whenDeviceIsNotCasting() {
-        doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
-        mAutoTileManager.mCastCallback.onCastDevicesChanged();
-        verify(mQsHost, never()).addTile("cast");
-    }
-
-    @Test
-    public void testSettingTileAdded_onChanged() {
-        changeValue(TEST_SETTING, 1);
-        verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
-        verify(mQsHost).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSettingTileAddedComponentAtEnd_onChanged() {
-        changeValue(TEST_SETTING_COMPONENT, 1);
-        verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
-        verify(mQsHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
-            , /* end */ true);
-    }
-
-    @Test
-    public void testSettingTileAdded_onlyOnce() {
-        changeValue(TEST_SETTING, 1);
-        changeValue(TEST_SETTING, 2);
-        verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
-        verify(mQsHost).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSettingTileNotAdded_onChangedTo0() {
-        changeValue(TEST_SETTING, 0);
-        verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
-        verify(mQsHost, never()).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSettingTileNotAdded_ifPreviouslyAdded() {
-        when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
-
-        changeValue(TEST_SETTING, 1);
-        verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
-        verify(mQsHost, never()).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSafetyTileNotAdded_ifPreviouslyAdded() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-        when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-    }
-
-    @Test
-    public void testSafetyTileAdded_onUserChange() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-        when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(false);
-        mAutoTileManager.changeUser(UserHandle.of(USER + 1));
-        verify(mQsHost, times(2)).addTile(safetyComponent, true);
-    }
-
-    @Test
-    public void testSafetyTileRemoved_onSafetyCenterDisable() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
-        mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
-        verify(mQsHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
-    }
-
-    @Test
-    public void testSafetyTileAdded_onSafetyCenterEnable() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-        mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
-        mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true);
-        verify(mQsHost, times(2)).addTile(safetyComponent, true);
-    }
-
-    @Test
-    public void managedProfileAdded_tileAdded() {
-        when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
-        when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2);
-        mAutoTileManager = createAutoTileManager(mContext);
-        Mockito.doAnswer((Answer<Object>) invocation -> {
-            mManagedProfileCallback = invocation.getArgument(0);
-            return null;
-        }).when(mManagedProfileController).addCallback(any());
-        mAutoTileManager.init();
-        when(mManagedProfileController.hasActiveProfile()).thenReturn(true);
-
-        mManagedProfileCallback.onManagedProfileChanged();
-
-        verify(mQsHost, times(1)).addTile(eq("work"), eq(2));
-        verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
-    }
-
-    @Test
-    public void managedProfileRemoved_tileRemoved() {
-        when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true);
-        mAutoTileManager = createAutoTileManager(mContext);
-        Mockito.doAnswer((Answer<Object>) invocation -> {
-            mManagedProfileCallback = invocation.getArgument(0);
-            return null;
-        }).when(mManagedProfileController).addCallback(any());
-        mAutoTileManager.init();
-        when(mManagedProfileController.hasActiveProfile()).thenReturn(false);
-
-        mManagedProfileCallback.onManagedProfileChanged();
-
-        verify(mQsHost, times(1)).removeTile(eq("work"));
-        verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
-    }
-
-    @Test
-    public void testAddControlsTileIfNotPresent() {
-        String spec = DEVICE_CONTROLS;
-        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
-        when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
-
-        mAutoTileManager.init();
-        ArgumentCaptor<DeviceControlsController.Callback> captor =
-                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
-        verify(mDeviceControlsController).setCallback(captor.capture());
-
-        captor.getValue().onControlsUpdate(3);
-        verify(mQsHost).addTile(spec, 3);
-        verify(mAutoAddTracker).setTileAdded(spec);
-    }
-
-    @Test
-    public void testDontAddControlsTileIfPresent() {
-        String spec = DEVICE_CONTROLS;
-        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
-        when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
-
-        mAutoTileManager.init();
-        ArgumentCaptor<DeviceControlsController.Callback> captor =
-                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
-        verify(mDeviceControlsController).setCallback(captor.capture());
-
-        captor.getValue().removeControlsAutoTracker();
-        verify(mQsHost, never()).addTile(spec, 3);
-        verify(mAutoAddTracker, never()).setTileAdded(spec);
-        verify(mAutoAddTracker).setTileRemoved(spec);
-    }
-
-    @Test
-    public void testRemoveControlsTileFromTrackerWhenRequested() {
-        String spec = "controls";
-        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
-        QSTile mockTile = mock(QSTile.class);
-        when(mockTile.getTileSpec()).thenReturn(spec);
-        when(mQsHost.getTiles()).thenReturn(List.of(mockTile));
-
-        mAutoTileManager.init();
-        ArgumentCaptor<DeviceControlsController.Callback> captor =
-                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
-        verify(mDeviceControlsController).setCallback(captor.capture());
-
-        captor.getValue().onControlsUpdate(3);
-        verify(mQsHost, never()).addTile(spec, 3);
-        verify(mAutoAddTracker, never()).setTileAdded(spec);
-    }
-
-
-    @Test
-    public void testEmptyArray_doesNotCrash() {
-        mContext.getOrCreateTestableResources().addOverride(
-                R.array.config_quickSettingsAutoAdd, new String[0]);
-        createAutoTileManager(mContext).destroy();
-    }
-
-    @Test
-    public void testMissingConfig_doesNotCrash() {
-        mContext.getOrCreateTestableResources().addOverride(
-                R.array.config_quickSettingsAutoAdd, null);
-        createAutoTileManager(mContext).destroy();
-    }
-
-    @Test
-    public void testUserChange_newNightDisplayListenerCreated() {
-        UserHandle newUser = UserHandle.of(1000);
-        mAutoTileManager.changeUser(newUser);
-        InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
-        inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
-        inOrder.verify(mNightDisplayListenerBuilder).build();
-    }
-
-    // Will only notify if it's listening
-    private void changeValue(String key, int value) {
-        mSecureSettings.putIntForUser(key, value, USER);
-        TestableLooper.get(this).processAllMessages();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 5b45781..30e7247 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,8 +33,11 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeControllerImpl
@@ -42,6 +45,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -70,7 +74,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
 
+    private val fakeDarkIconDispatcher = kosmos.fakeDarkIconDispatcher
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
     @Mock private lateinit var featureFlags: FeatureFlags
@@ -91,6 +97,12 @@
     private lateinit var view: PhoneStatusBarView
     private lateinit var controller: PhoneStatusBarViewController
 
+    private val clockView: Clock
+        get() = view.requireViewById(R.id.clock)
+
+    private val batteryView: BatteryMeterView
+        get() = view.requireViewById(R.id.battery)
+
     private val unfoldConfig = UnfoldConfig()
 
     @Before
@@ -114,16 +126,25 @@
     @Test
     fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() {
         val view = createViewMock()
-        val argumentCaptor =
-            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             controller = createAndInitController(view)
         }
 
-        verify(configurationController).addCallback(argumentCaptor.capture())
-        argumentCaptor.value.onDensityOrFontScaleChanged()
+        verify(configurationController).addCallback(any())
+    }
 
-        verify(view).onDensityOrFontScaleChanged()
+    @Test
+    fun onViewAttachedAndDrawn_darkReceiversRegistered() {
+        val view = createViewMock()
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            controller = createAndInitController(view)
+        }
+
+        assertThat(fakeDarkIconDispatcher.receivers.size).isEqualTo(2)
+        assertThat(fakeDarkIconDispatcher.receivers).contains(clockView)
+        assertThat(fakeDarkIconDispatcher.receivers).contains(batteryView)
     }
 
     @Test
@@ -158,6 +179,21 @@
     }
 
     @Test
+    fun onViewDetached_darkReceiversUnregistered() {
+        val view = createViewMock()
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            controller = createAndInitController(view)
+        }
+
+        assertThat(fakeDarkIconDispatcher.receivers).isNotEmpty()
+
+        controller.onViewDetached()
+
+        assertThat(fakeDarkIconDispatcher.receivers).isEmpty()
+    }
+
+    @Test
     fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
         val returnVal =
@@ -353,7 +389,8 @@
                 shadeLogger,
                 viewUtil,
                 configurationController,
-                mStatusOverlayHoverListenerFactory
+                mStatusOverlayHoverListenerFactory,
+                fakeDarkIconDispatcher,
             )
             .create(view)
             .also { it.init() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index abc50bc..ed5ec7b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.mockito.mock
@@ -64,7 +63,6 @@
             StatusBarContentInsetsProvider::class.java,
             contentInsetsProvider
         )
-        mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>())
         mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
         context.ensureTestableResources()
         view = spy(createStatusBarView())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index e70631e..e8612d08 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.applicationContext
@@ -26,26 +28,31 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
 import com.android.systemui.util.time.systemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@ExperimentalCoroutinesApi
-val Kosmos.bouncerMessageViewModel by
-    Kosmos.Fixture {
-        BouncerMessageViewModel(
-            applicationContext = applicationContext,
-            applicationScope = testScope.backgroundScope,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            selectedUser = userSwitcherViewModel.selectedUser,
-            clock = systemClock,
-            biometricMessageInteractor = biometricMessageInteractor,
-            faceAuthInteractor = deviceEntryFaceAuthInteractor,
-            deviceUnlockedInteractor = deviceUnlockedInteractor,
-            deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
-            flags = composeBouncerFlags,
-        )
+val Kosmos.bouncerMessageViewModel by Fixture {
+    BouncerMessageViewModel(
+        applicationContext = applicationContext,
+        bouncerInteractor = bouncerInteractor,
+        simBouncerInteractor = simBouncerInteractor,
+        authenticationInteractor = authenticationInteractor,
+        userSwitcherViewModel = userSwitcherViewModel,
+        clock = systemClock,
+        biometricMessageInteractor = biometricMessageInteractor,
+        faceAuthInteractor = deviceEntryFaceAuthInteractor,
+        deviceUnlockedInteractor = deviceUnlockedInteractor,
+        deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
+        flags = composeBouncerFlags,
+    )
+}
+
+val Kosmos.bouncerMessageViewModelFactory by Fixture {
+    object : BouncerMessageViewModel.Factory {
+        override fun create(): BouncerMessageViewModel {
+            return bouncerMessageViewModel
+        }
     }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index c3dad74..e405d17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import android.app.admin.devicePolicyManager
 import android.content.applicationContext
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
@@ -28,28 +29,97 @@
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.StateFlow
 
-val Kosmos.bouncerViewModel by Fixture {
-    BouncerViewModel(
-        applicationContext = applicationContext,
-        applicationScope = testScope.backgroundScope,
-        mainDispatcher = testDispatcher,
+val Kosmos.bouncerSceneActionsViewModel by Fixture {
+    BouncerSceneActionsViewModel(
         bouncerInteractor = bouncerInteractor,
-        inputMethodInteractor = inputMethodInteractor,
-        simBouncerInteractor = simBouncerInteractor,
-        authenticationInteractor = authenticationInteractor,
-        selectedUserInteractor = selectedUserInteractor,
-        devicePolicyManager = devicePolicyManager,
-        bouncerMessageViewModel = bouncerMessageViewModel,
-        flags = composeBouncerFlags,
-        selectedUser = userSwitcherViewModel.selectedUser,
-        users = userSwitcherViewModel.users,
-        userSwitcherMenu = userSwitcherViewModel.menu,
-        actionButton = bouncerActionButtonInteractor.actionButton,
     )
 }
+
+val Kosmos.bouncerSceneActionsViewModelFactory by Fixture {
+    object : BouncerSceneActionsViewModel.Factory {
+        override fun create(): BouncerSceneActionsViewModel {
+            return bouncerSceneActionsViewModel
+        }
+    }
+}
+
+val Kosmos.bouncerSceneContentViewModel by Fixture {
+    BouncerSceneContentViewModel(
+        applicationContext = applicationContext,
+        bouncerInteractor = bouncerInteractor,
+        authenticationInteractor = authenticationInteractor,
+        devicePolicyManager = devicePolicyManager,
+        bouncerMessageViewModelFactory = bouncerMessageViewModelFactory,
+        flags = composeBouncerFlags,
+        userSwitcher = userSwitcherViewModel,
+        actionButtonInteractor = bouncerActionButtonInteractor,
+        pinViewModelFactory = pinBouncerViewModelFactory,
+        patternViewModelFactory = patternBouncerViewModelFactory,
+        passwordViewModelFactory = passwordBouncerViewModelFactory,
+    )
+}
+
+val Kosmos.bouncerSceneContentViewModelFactory by Fixture {
+    object : BouncerSceneContentViewModel.Factory {
+        override fun create(): BouncerSceneContentViewModel {
+            return bouncerSceneContentViewModel
+        }
+    }
+}
+
+val Kosmos.pinBouncerViewModelFactory by Fixture {
+    object : PinBouncerViewModel.Factory {
+        override fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+            authenticationMethod: AuthenticationMethodModel,
+        ): PinBouncerViewModel {
+            return PinBouncerViewModel(
+                applicationContext = applicationContext,
+                interactor = bouncerInteractor,
+                simBouncerInteractor = simBouncerInteractor,
+                isInputEnabled = isInputEnabled,
+                onIntentionalUserInput = onIntentionalUserInput,
+                authenticationMethod = authenticationMethod,
+            )
+        }
+    }
+}
+
+val Kosmos.patternBouncerViewModelFactory by Fixture {
+    object : PatternBouncerViewModel.Factory {
+        override fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PatternBouncerViewModel {
+            return PatternBouncerViewModel(
+                applicationContext = applicationContext,
+                interactor = bouncerInteractor,
+                isInputEnabled = isInputEnabled,
+                onIntentionalUserInput = onIntentionalUserInput,
+            )
+        }
+    }
+}
+
+val Kosmos.passwordBouncerViewModelFactory by Fixture {
+    object : PasswordBouncerViewModel.Factory {
+        override fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PasswordBouncerViewModel {
+            return PasswordBouncerViewModel(
+                interactor = bouncerInteractor,
+                inputMethodInteractor = inputMethodInteractor,
+                selectedUserInteractor = selectedUserInteractor,
+                isInputEnabled = isInputEnabled,
+                onIntentionalUserInput = onIntentionalUserInput,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
new file mode 100644
index 0000000..8124224
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.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.communal.domain.interactor
+
+import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shared.system.taskStackChangeListeners
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.widgetTrampolineInteractor: WidgetTrampolineInteractor by
+    Kosmos.Fixture {
+        WidgetTrampolineInteractor(
+            activityStarter = activityStarter,
+            systemClock = fakeSystemClock,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            taskStackChangeListeners = taskStackChangeListeners,
+            usageStatsInteractor = usageStatsInteractor,
+            logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 4571c19..54a6c0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -60,13 +60,13 @@
     override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
 
     private val _isKeyguardShowing = MutableStateFlow(false)
-    override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+    override val isKeyguardShowing: StateFlow<Boolean> = _isKeyguardShowing
 
     private val _isKeyguardUnlocked = MutableStateFlow(false)
     override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
 
     private val _isKeyguardOccluded = MutableStateFlow(false)
-    override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
+    override val isKeyguardOccluded: StateFlow<Boolean> = _isKeyguardOccluded
 
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: StateFlow<Boolean> = _isDozing
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt
new file mode 100644
index 0000000..3d125e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.plugins
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.fakeDarkIconDispatcher: FakeDarkIconDispatcher by
+    Kosmos.Fixture { FakeDarkIconDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt
new file mode 100644
index 0000000..102a853
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.plugins
+
+import android.graphics.Rect
+import java.util.ArrayList
+
+class FakeDarkIconDispatcher : DarkIconDispatcher {
+    val receivers = mutableListOf<DarkIconDispatcher.DarkReceiver>()
+
+    override fun setIconsDarkArea(r: ArrayList<Rect>) {}
+
+    override fun addDarkReceiver(receiver: DarkIconDispatcher.DarkReceiver) {
+        receivers.add(receiver)
+    }
+
+    override fun removeDarkReceiver(receiver: DarkIconDispatcher.DarkReceiver) {
+        receivers.remove(receiver)
+    }
+
+    override fun applyDark(`object`: DarkIconDispatcher.DarkReceiver) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt
new file mode 100644
index 0000000..67f611a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.shared.system
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.taskStackChangeListeners: TaskStackChangeListeners by
+    Kosmos.Fixture { TaskStackChangeListeners.getTestInstance() }
diff --git a/services/contentcapture/java/com/android/server/contentprotection/OWNERS b/services/contentcapture/java/com/android/server/contentprotection/OWNERS
new file mode 100644
index 0000000..3d09da3
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1040349
+
+include /core/java/android/view/contentprotection/OWNERS
+
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index f61bd60..154b52b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1413,6 +1413,22 @@
     }
 
     public void uidRemoved(int uid) {
+        if (Flags.dontRemoveExistingUidStates()) {
+            // b/358365471 If apps sharing UID are installed on multiple users and only one of
+            // them is installed for a single user while keeping the others we observe this
+            // subroutine get invoked incorrectly since the UID still exists.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                String uidName = getPackageManagerInternal().getNameForUid(uid);
+                if (uidName != null) {
+                    Slog.e(TAG, "Tried to remove existing UID. uid: " + uid + " name: " + uidName);
+                    return;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         synchronized (this) {
             if (mUidStates.indexOfKey(uid) >= 0) {
                 mUidStates.get(uid).clear();
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 226bdf5..240e91b 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
 import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX;
 import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
 
@@ -241,6 +243,9 @@
 
     private int mDisplayState = Display.STATE_UNKNOWN;
 
+    // True if the normal brightness should be forced while device is dozing.
+    private boolean mUseNormalBrightnessForDoze;
+
     // True if we are collecting a brightness adjustment sample, along with some data
     // for the initial state of the sample.
     private boolean mBrightnessAdjustmentSamplePending;
@@ -442,11 +447,12 @@
     public void configure(int state, @Nullable BrightnessConfiguration configuration,
             float brightness, boolean userChangedBrightness, float adjustment,
             boolean userChangedAutoBrightnessAdjustment, int displayPolicy, int displayState,
-            boolean shouldResetShortTermModel) {
+            boolean useNormalBrightnessForDoze, boolean shouldResetShortTermModel) {
         mState = state;
         boolean changed = setBrightnessConfiguration(configuration, shouldResetShortTermModel);
         changed |= setDisplayPolicy(displayPolicy);
         mDisplayState = displayState;
+        mUseNormalBrightnessForDoze = useNormalBrightnessForDoze;
         if (userChangedAutoBrightnessAdjustment) {
             changed |= setAutoBrightnessAdjustment(adjustment);
         }
@@ -1264,11 +1270,10 @@
     }
 
     private boolean shouldApplyDozeScaleFactor() {
-        // Apply the doze scale factor if the display is in doze. We shouldn't rely on the display
-        // policy here - the screen might turn on while the policy is POLICY_DOZE and in this
-        // situation, we shouldn't apply the doze scale factor. We also don't apply the doze scale
-        // factor if we have a designated brightness curve for doze.
-        return Display.isDozeState(mDisplayState) && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
+        // We don't apply the doze scale factor if we have a designated brightness curve for doze.
+        return (mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()
+                ? !mUseNormalBrightnessForDoze && mDisplayPolicy == POLICY_DOZE
+                : Display.isDozeState(mDisplayState)) && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
     }
 
     private class ShortTermModel {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 8bb33dd..bd27f47 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1419,7 +1419,7 @@
 
             mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
                     allowAutoBrightnessWhileDozing, mBrightnessReasonTemp.getReason(),
-                    mPowerRequest.policy,
+                    mPowerRequest.policy, mPowerRequest.useNormalBrightnessForDoze,
                     mDisplayBrightnessController.getLastUserSetScreenBrightness(),
                     userSetBrightnessChanged);
 
@@ -1485,7 +1485,9 @@
             brightnessState = clampScreenBrightness(brightnessState);
         }
 
-        if (Display.isDozeState(state)) {
+        if (mFlags.isNormalBrightnessForDozeParameterEnabled()
+                ? !mPowerRequest.useNormalBrightnessForDoze && mPowerRequest.policy == POLICY_DOZE
+                : Display.isDozeState(state)) {
             // TODO(b/329676661): Introduce a config property to choose between this brightness
             //  strategy and DOZE_DEFAULT
             // On some devices, when auto-brightness is disabled and the device is dozing, we use
@@ -1506,7 +1508,7 @@
             }
 
             // Use default brightness when dozing unless overridden.
-            if (Float.isNaN(brightnessState) && Display.isDozeState(state)
+            if (Float.isNaN(brightnessState)
                     && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
                 rawBrightnessState = mScreenBrightnessDozeConfig;
                 brightnessState = clampScreenBrightness(rawBrightnessState);
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 7835220..1b49bbc 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.view.Display;
@@ -167,7 +168,7 @@
             StrategySelectionRequest strategySelectionRequest) {
         DisplayBrightnessStrategy displayBrightnessStrategy = mInvalidBrightnessStrategy;
         int targetDisplayState = strategySelectionRequest.getTargetDisplayState();
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = strategySelectionRequest
+        DisplayPowerRequest displayPowerRequest = strategySelectionRequest
                 .getDisplayPowerRequest();
         setAllowAutoBrightnessWhileDozing(strategySelectionRequest.getDisplayOffloadSession());
         if (targetDisplayState == Display.STATE_OFF) {
@@ -301,6 +302,7 @@
                 mAllowAutoBrightnessWhileDozing,
                 BrightnessReason.REASON_UNKNOWN,
                 strategySelectionRequest.getDisplayPowerRequest().policy,
+                strategySelectionRequest.getDisplayPowerRequest().useNormalBrightnessForDoze,
                 strategySelectionRequest.getLastUserSetScreenBrightness(),
                 strategySelectionRequest.isUserSetBrightnessChanged());
         return mAutomaticBrightnessStrategy1.isAutoBrightnessValid();
@@ -331,12 +333,14 @@
     /**
      * Validates if the conditions are met to qualify for the DozeBrightnessStrategy.
      */
-    private boolean shouldUseDozeBrightnessStrategy(
-            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+    private boolean shouldUseDozeBrightnessStrategy(DisplayPowerRequest displayPowerRequest) {
         // We are not checking the targetDisplayState, but rather relying on the policy because
         // a user can define a different display state(displayPowerRequest.dozeScreenState) too
-        // in the request with the Doze policy
-        return displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+        // in the request with the Doze policy and user might request an override to force certain
+        // brightness.
+        return (!mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()
+                || !displayPowerRequest.useNormalBrightnessForDoze)
+                && displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
                 && !mAllowAutoBrightnessWhileDozing
                 && BrightnessUtils.isValidBrightnessValue(displayPowerRequest.dozeScreenBrightness);
     }
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 706b4a6..bf01f2d 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
@@ -131,13 +131,14 @@
      */
     public void setAutoBrightnessState(int targetDisplayState,
             boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
-            float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+            boolean useNormalBrightnessForDoze, float lastUserSetScreenBrightness,
+            boolean userSetBrightnessChanged) {
         // We are still in the process of updating the power state, so there's no need to trigger
         // an update again
-        switchMode(targetDisplayState, /* sendUpdate= */ false);
+        switchMode(targetDisplayState, useNormalBrightnessForDoze, policy, /* sendUpdate= */ false);
 
-        // If the policy is POLICY_DOZE and the display state is STATE_ON, auto-brightness should
-        // only be enabled if the config allows it
+        // If the policy is POLICY_DOZE and the display state is not STATE_OFF, auto-brightness
+        // should only be enabled if the config allows it
         final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig
                 && policy == POLICY_DOZE && targetDisplayState != Display.STATE_OFF;
 
@@ -157,7 +158,8 @@
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 
         accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
-                policy, targetDisplayState, mBrightnessConfiguration, autoBrightnessState);
+                policy, targetDisplayState, useNormalBrightnessForDoze, mBrightnessConfiguration,
+                autoBrightnessState);
         mIsConfigured = true;
     }
 
@@ -344,6 +346,8 @@
                     strategySelectionNotifyRequest.getSelectedDisplayBrightnessStrategy()
                             .getReason(),
                     strategySelectionNotifyRequest.getDisplayPowerRequest().policy,
+                    strategySelectionNotifyRequest.getDisplayPowerRequest()
+                            .useNormalBrightnessForDoze,
                     strategySelectionNotifyRequest.getLastUserSetScreenBrightness(),
                     strategySelectionNotifyRequest.isUserSetBrightnessChanged());
         }
@@ -469,7 +473,8 @@
     @VisibleForTesting
     void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
             float lastUserSetScreenBrightness, int policy, int displayState,
-            BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+            boolean useNormalBrightnessForDoze, 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
@@ -485,8 +490,12 @@
             mAutomaticBrightnessController.configure(autoBrightnessState,
                     brightnessConfiguration,
                     lastUserSetScreenBrightness,
-                    userSetBrightnessChanged, autoBrightnessAdjustment,
-                    mAutoBrightnessAdjustmentChanged, policy, displayState,
+                    userSetBrightnessChanged,
+                    autoBrightnessAdjustment,
+                    mAutoBrightnessAdjustmentChanged,
+                    policy,
+                    displayState,
+                    useNormalBrightnessForDoze,
                     mShouldResetShortTermModel);
             mShouldResetShortTermModel = false;
             // We take note if the user brightness point is still being used in the current
@@ -494,11 +503,18 @@
             mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
         }
     }
-    private void switchMode(int state, boolean sendUpdate) {
+
+    private void switchMode(int state, boolean useNormalBrightnessForDoze, int policy,
+            boolean sendUpdate) {
         if (mDisplayManagerFlags.areAutoBrightnessModesEnabled()
                 && mAutomaticBrightnessController != null
                 && !mAutomaticBrightnessController.isInIdleMode()) {
-            mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
+
+            boolean shouldUseDozeMode =
+                    mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()
+                            ? !useNormalBrightnessForDoze && policy == POLICY_DOZE
+                            : Display.isDozeState(state);
+            mAutomaticBrightnessController.switchMode(shouldUseDozeMode
                     ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT, sendUpdate);
         }
     }
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
index c87872c..5e79565 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -108,9 +108,10 @@
      */
     public void setAutoBrightnessState(int targetDisplayState,
             boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
-            float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
-        // If the policy is POLICY_DOZE and the display state is STATE_ON, auto-brightness should
-        // only be enabled if the config allows it
+            boolean useNormalBrightnessForDoze, float lastUserSetScreenBrightness,
+            boolean userSetBrightnessChanged) {
+        // If the policy is POLICY_DOZE and the display state is not STATE_OFF, auto-brightness
+        // should only be enabled if the config allows it
         final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig
                 && policy == POLICY_DOZE && targetDisplayState != Display.STATE_OFF;
 
@@ -120,7 +121,7 @@
                 && brightnessReason != BrightnessReason.REASON_OVERRIDE
                 && mAutomaticBrightnessController != null;
         mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
-                && !((targetDisplayState == Display.STATE_ON  && policy != POLICY_DOZE)
+                && !((targetDisplayState == Display.STATE_ON && policy != POLICY_DOZE)
                 || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = mIsAutoBrightnessEnabled
                 && brightnessReason != BrightnessReason.REASON_FOLLOWER
@@ -130,7 +131,8 @@
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 
         accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
-                policy, targetDisplayState, mBrightnessConfiguration, autoBrightnessState);
+                policy, targetDisplayState, useNormalBrightnessForDoze, mBrightnessConfiguration,
+                autoBrightnessState);
     }
 
     public boolean isAutoBrightnessEnabled() {
@@ -367,7 +369,8 @@
     @VisibleForTesting
     void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
             float lastUserSetScreenBrightness, int policy, int displayState,
-            BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+            boolean useNormalBrightnessForDoze, 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
@@ -384,7 +387,10 @@
                     brightnessConfiguration,
                     lastUserSetScreenBrightness,
                     userSetBrightnessChanged, autoBrightnessAdjustment,
-                    mAutoBrightnessAdjustmentChanged, policy, displayState,
+                    mAutoBrightnessAdjustmentChanged,
+                    policy,
+                    displayState,
+                    useNormalBrightnessForDoze,
                     mShouldResetShortTermModel);
             mShouldResetShortTermModel = false;
             // We take note if the user brightness point is still being used in the current
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index e1934b0..f0f6db2 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -193,6 +193,11 @@
                     Flags.FLAG_NEW_HDR_BRIGHTNESS_MODIFIER,
                     Flags::newHdrBrightnessModifier);
 
+    private final FlagState mNormalBrightnessForDozeParameter = new FlagState(
+            Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
+            Flags::normalBrightnessForDozeParameter
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -393,6 +398,13 @@
     }
 
     /**
+     * @return Whether the useDozeBrightness parameter should be used
+     */
+    public boolean isNormalBrightnessForDozeParameterEnabled() {
+        return mNormalBrightnessForDozeParameter.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -431,6 +443,7 @@
         pw.println(" " + mOffloadDozeOverrideHoldsWakelock);
         pw.println(" " + mOffloadSessionCancelBlockScreenOn);
         pw.println(" " + mNewHdrBrightnessModifier);
+        pw.println(" " + mNormalBrightnessForDozeParameter);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index ac5f97f..d929249 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -250,6 +250,13 @@
     namespace: "display_manager"
     description: "Define doze brightness in the float scale [0, 1]."
     bug: "343796384"
+}
+
+flag {
+    name: "normal_brightness_for_doze_parameter"
+    namespace: "wear_frameworks"
+    description: "Feature flag for the parameter specifying whether brightness should be adjusted while dozing."
+    bug: "343283838"
     is_fixed_read_only: true
 }
 
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 5e471c8..31f5a41 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1592,6 +1592,12 @@
                                 - SYNCHRONIZED_REFRESH_RATE_TOLERANCE,
                             SYNCHRONIZED_REFRESH_RATE_TARGET
                                 + SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE,
+                    Vote.forRenderFrameRates(
+                            SYNCHRONIZED_REFRESH_RATE_TARGET
+                                    - SYNCHRONIZED_REFRESH_RATE_TOLERANCE,
+                            SYNCHRONIZED_REFRESH_RATE_TARGET
+                                    + SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
         }
 
         private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) {
@@ -1603,11 +1609,12 @@
                     return;
                 }
                 mExternalDisplaysConnected.remove(displayId);
-                if (mExternalDisplaysConnected.size() != 0) {
+                if (!mExternalDisplaysConnected.isEmpty()) {
                     return;
                 }
             }
             mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, null);
         }
 
         private void updateDisplayDeviceConfig(int displayId) {
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 7cbdd13..88ee04481 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -92,48 +92,55 @@
     // render rate vote can still apply
     int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 9;
 
-    // Restrict all displays to 60Hz when external display is connected. It votes [59Hz, 61Hz].
+    // Restrict all displays physical refresh rate to 60Hz when external display is connected.
+    // It votes [59Hz, 61Hz].
     int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 10;
 
+    // PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE has a higher priority than
+    // PRIORITY_SYNCHRONIZED_REFRESH_RATE and will limit render rate to [59Hz, 61Hz].
+    // In case physical refresh rate vote discarded (due to physical refresh rate not supported),
+    // render rate vote can still apply.
+    int PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE = 11;
+
     // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT]
-    int PRIORITY_LIMIT_MODE = 11;
+    int PRIORITY_LIMIT_MODE = 12;
 
     // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
     // rate to max value (same as for PRIORITY_UDFPS) on lock screen
-    int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 12;
+    int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13;
 
     // For concurrent displays we want to limit refresh rate on all displays
-    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 13;
+    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 14;
 
     // For internal application to limit display modes to specific ids
-    int PRIORITY_SYSTEM_REQUESTED_MODES = 14;
+    int PRIORITY_SYSTEM_REQUESTED_MODES = 15;
 
     // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
     // Settings.Global.LOW_POWER_MODE is on.
     // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
     // higher priority votes), render rate limit can still apply
-    int PRIORITY_LOW_POWER_MODE_MODES = 15;
+    int PRIORITY_LOW_POWER_MODE_MODES = 16;
 
     // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
     // Settings.Global.LOW_POWER_MODE is on.
-    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 16;
+    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 17;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
     // It's used to avoid refresh rate switches in certain conditions which may result in the
     // user seeing the display flickering when the switches occur.
-    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 17;
+    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 18;
 
     // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-    int PRIORITY_SKIN_TEMPERATURE = 18;
+    int PRIORITY_SKIN_TEMPERATURE = 19;
 
     // The proximity sensor needs the refresh rate to be locked in order to function, so this is
     // set to a high priority.
-    int PRIORITY_PROXIMITY = 19;
+    int PRIORITY_PROXIMITY = 20;
 
     // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
     // to function, so this needs to be the highest priority of all votes.
-    int PRIORITY_UDFPS = 20;
+    int PRIORITY_UDFPS = 21;
 
     @IntDef(prefix = { "PRIORITY_" }, value = {
             PRIORITY_DEFAULT_RENDER_FRAME_RATE,
@@ -147,6 +154,7 @@
             PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
             PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
             PRIORITY_SYNCHRONIZED_REFRESH_RATE,
+            PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE,
             PRIORITY_LIMIT_MODE,
             PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE,
             PRIORITY_LAYOUT_LIMITED_FRAME_RATE,
@@ -267,6 +275,8 @@
                 return "PRIORITY_LIMIT_MODE";
             case PRIORITY_SYNCHRONIZED_REFRESH_RATE:
                 return "PRIORITY_SYNCHRONIZED_REFRESH_RATE";
+            case PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE:
+                return "PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE";
             case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
                 return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
             case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index a3b77e8..19305de 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -543,12 +543,14 @@
     }
 
     private void startDozingInternal(IBinder token, int screenState,
-            @Display.StateReason int reason, float screenBrightnessFloat, int screenBrightnessInt) {
+            @Display.StateReason int reason, float screenBrightnessFloat, int screenBrightnessInt,
+            boolean useNormalBrightnessForDoze) {
         Slog.d(TAG, "Dream requested to start dozing: " + token
                 + ", screenState=" + Display.stateToString(screenState)
                 + ", reason=" + Display.stateReasonToString(reason)
                 + ", screenBrightnessFloat=" + screenBrightnessFloat
-                + ", screenBrightnessInt=" + screenBrightnessInt);
+                + ", screenBrightnessInt=" + screenBrightnessInt
+                + ", useNormalBrightnessForDoze=" + useNormalBrightnessForDoze);
 
         synchronized (mLock) {
             if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
@@ -556,7 +558,8 @@
                 mCurrentDream.dozeScreenBrightness = screenBrightnessInt;
                 mCurrentDream.dozeScreenBrightnessFloat = screenBrightnessFloat;
                 mPowerManagerInternal.setDozeOverrideFromDreamManager(
-                        screenState, reason, screenBrightnessFloat, screenBrightnessInt);
+                        screenState, reason, screenBrightnessFloat, screenBrightnessInt,
+                        useNormalBrightnessForDoze);
                 if (!mCurrentDream.isDozing) {
                     mCurrentDream.isDozing = true;
                     mDozeWakeLock.acquire();
@@ -578,7 +581,8 @@
                         Display.STATE_UNKNOWN,
                         Display.STATE_REASON_DREAM_MANAGER,
                         PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                        PowerManager.BRIGHTNESS_DEFAULT);
+                        PowerManager.BRIGHTNESS_DEFAULT,
+                        /* useNormalBrightnessForDoze= */ false);
             }
         }
     }
@@ -1098,7 +1102,8 @@
         @Override // Binder call
         public void startDozing(
                 IBinder token, int screenState, @Display.StateReason int reason,
-                float screenBrightnessFloat, int screeBrightnessInt) {
+                float screenBrightnessFloat, int screeBrightnessInt,
+                boolean useNormalBrightnessForDoze) {
             // Requires no permission, called by Dream from an arbitrary process.
             if (token == null) {
                 throw new IllegalArgumentException("token must not be null");
@@ -1107,7 +1112,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 startDozingInternal(token, screenState, reason, screenBrightnessFloat,
-                        screeBrightnessInt);
+                        screeBrightnessInt, useNormalBrightnessForDoze);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1116,7 +1121,8 @@
         @Override // Binder call
         public void startDozingOneway(
                 IBinder token, int screenState, @Display.StateReason int reason,
-                float screenBrightnessFloat, int screeBrightnessInt) {
+                float screenBrightnessFloat, int screeBrightnessInt,
+                boolean useNormalBrightnessForDoze) {
             // Requires no permission, called by Dream from an arbitrary process.
             if (token == null) {
                 throw new IllegalArgumentException("token must not be null");
@@ -1125,7 +1131,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 startDozingInternal(token, screenState, reason, screenBrightnessFloat,
-                        screeBrightnessInt);
+                        screeBrightnessInt, useNormalBrightnessForDoze);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 819b9a1..73f18d1 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -21,7 +21,7 @@
 import android.annotation.UserIdInt;
 import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.os.IBinder;
 import android.view.InputChannel;
 import android.view.inputmethod.InputMethodSubtype;
@@ -230,18 +230,14 @@
     public abstract int getLastUsedInputDeviceId();
 
     /**
-     * Notify Keyboard system shortcut was triggered by the user and handled by the framework.
+     * Notify key gesture was completed by the user.
      *
-     * NOTE: This is just to notify that a system shortcut was triggered. No further action is
-     * required to execute the said shortcut. This callback is meant for purposes of providing user
-     * hints or logging, etc.
-     *
-     * @param deviceId the device ID of the keyboard using which the shortcut was triggered
-     * @param keycodes the keys pressed for triggering the shortcut
-     * @param modifierState the modifier state of the key event that triggered the shortcut
-     * @param shortcut the shortcut that was triggered
+     * @param deviceId the device ID of the keyboard using which the event was completed
+     * @param keycodes the keys pressed for the event
+     * @param modifierState the modifier state
+     * @param event the gesture event that was completed
      *
      */
-    public abstract void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes,
-            int modifierState, @KeyboardSystemShortcut.SystemShortcut int shortcut);
+    public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
+            @KeyGestureEvent.KeyGestureType int event);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a69c7ef..a8fc862 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -48,18 +48,18 @@
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
+import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IKeyboardBacklightListener;
-import android.hardware.input.IKeyboardSystemShortcutListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGestureEvent;
 import android.hardware.input.KeyGlyphMap;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult;
-import android.hardware.input.KeyboardSystemShortcut;
 import android.hardware.input.TouchCalibration;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -162,7 +162,7 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
-    private static final int MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED = 4;
+    private static final int MSG_KEY_GESTURE_COMPLETED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -314,9 +314,7 @@
 
     // Manages Sticky modifier state
     private final StickyModifierStateController mStickyModifierStateController;
-
-    // Manages keyboard system shortcut callbacks
-    private final KeyboardShortcutCallbackHandler mKeyboardShortcutCallbackHandler;
+    private final KeyGestureController mKeyGestureController;
 
     // Manages Keyboard microphone mute led
     private final KeyboardLedController mKeyboardLedController;
@@ -476,7 +474,7 @@
                         injector.getLooper(), injector.getUEventManager())
                 : new KeyboardBacklightControllerInterface() {};
         mStickyModifierStateController = new StickyModifierStateController();
-        mKeyboardShortcutCallbackHandler = new KeyboardShortcutCallbackHandler();
+        mKeyGestureController = new KeyGestureController();
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
                 mNative);
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -2723,33 +2721,32 @@
     }
 
     @Override
-    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void registerKeyboardSystemShortcutListener(
-            @NonNull IKeyboardSystemShortcutListener listener) {
-        super.registerKeyboardSystemShortcutListener_enforcePermission();
+    @EnforcePermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void registerKeyGestureEventListener(
+            @NonNull IKeyGestureEventListener listener) {
+        super.registerKeyGestureEventListener_enforcePermission();
         Objects.requireNonNull(listener);
-        mKeyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener,
+        mKeyGestureController.registerKeyGestureEventListener(listener,
                 Binder.getCallingPid());
     }
 
     @Override
-    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void unregisterKeyboardSystemShortcutListener(
-            @NonNull IKeyboardSystemShortcutListener listener) {
-        super.unregisterKeyboardSystemShortcutListener_enforcePermission();
+    @EnforcePermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void unregisterKeyGestureEventListener(
+            @NonNull IKeyGestureEventListener listener) {
+        super.unregisterKeyGestureEventListener_enforcePermission();
         Objects.requireNonNull(listener);
-        mKeyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener,
+        mKeyGestureController.unregisterKeyGestureEventListener(listener,
                 Binder.getCallingPid());
     }
 
-    private void handleKeyboardSystemShortcutTriggered(int deviceId,
-            KeyboardSystemShortcut shortcut) {
-        InputDevice device = getInputDevice(deviceId);
-        if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+    private void handleKeyGestureCompleted(KeyGestureEvent event) {
+        InputDevice device = getInputDevice(event.getDeviceId());
+        if (device == null || device.isVirtual()) {
             return;
         }
-        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, shortcut);
-        mKeyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(deviceId, shortcut);
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, event);
+        mKeyGestureController.onKeyGestureEvent(event);
     }
 
     /**
@@ -2920,10 +2917,9 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
-                case MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED:
-                    int deviceId = msg.arg1;
-                    KeyboardSystemShortcut shortcut = (KeyboardSystemShortcut) msg.obj;
-                    handleKeyboardSystemShortcutTriggered(deviceId, shortcut);
+                case MSG_KEY_GESTURE_COMPLETED:
+                    KeyGestureEvent event = (KeyGestureEvent) msg.obj;
+                    handleKeyGestureCompleted(event);
             }
         }
     }
@@ -3251,10 +3247,11 @@
         }
 
         @Override
-        public void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, int modifierState,
-                @KeyboardSystemShortcut.SystemShortcut int shortcut) {
-            mHandler.obtainMessage(MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED, deviceId, 0,
-                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut)).sendToTarget();
+        public void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
+                @KeyGestureEvent.KeyGestureType int gestureType) {
+            mHandler.obtainMessage(MSG_KEY_GESTURE_COMPLETED,
+                    new KeyGestureEvent(deviceId, keycodes, modifierState,
+                            gestureType)).sendToTarget();
         }
     }
 
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
new file mode 100644
index 0000000..674d3c4
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 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.input;
+
+import android.annotation.BinderThread;
+import android.hardware.input.IKeyGestureEventListener;
+import android.hardware.input.KeyGestureEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
+ * key gesture event occurs.
+ */
+final class KeyGestureController {
+
+    private static final String TAG = "KeyGestureController";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.KeyGestureController DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // List of currently registered key gesture event listeners keyed by process pid
+    @GuardedBy("mKeyGestureEventListenerRecords")
+    private final SparseArray<KeyGestureEventListenerRecord>
+            mKeyGestureEventListenerRecords = new SparseArray<>();
+
+    public void onKeyGestureEvent(KeyGestureEvent event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Key gesture event occurred, event = " + event);
+        }
+
+        synchronized (mKeyGestureEventListenerRecords) {
+            for (int i = 0; i < mKeyGestureEventListenerRecords.size(); i++) {
+                mKeyGestureEventListenerRecords.valueAt(i).onKeyGestureEvent(event);
+            }
+        }
+    }
+
+    /** Register the key gesture event listener for a process. */
+    @BinderThread
+    public void registerKeyGestureEventListener(IKeyGestureEventListener listener,
+            int pid) {
+        synchronized (mKeyGestureEventListenerRecords) {
+            if (mKeyGestureEventListenerRecords.get(pid) != null) {
+                throw new IllegalStateException("The calling process has already registered "
+                        + "a KeyGestureEventListener.");
+            }
+            KeyGestureEventListenerRecord record = new KeyGestureEventListenerRecord(
+                    pid, listener);
+            try {
+                listener.asBinder().linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(ex);
+            }
+            mKeyGestureEventListenerRecords.put(pid, record);
+        }
+    }
+
+    /** Unregister the key gesture event listener for a process. */
+    @BinderThread
+    public void unregisterKeyGestureEventListener(IKeyGestureEventListener listener,
+            int pid) {
+        synchronized (mKeyGestureEventListenerRecords) {
+            KeyGestureEventListenerRecord record =
+                    mKeyGestureEventListenerRecords.get(pid);
+            if (record == null) {
+                throw new IllegalStateException("The calling process has no registered "
+                        + "KeyGestureEventListener.");
+            }
+            if (record.mListener.asBinder() != listener.asBinder()) {
+                throw new IllegalStateException("The calling process has a different registered "
+                        + "KeyGestureEventListener.");
+            }
+            record.mListener.asBinder().unlinkToDeath(record, 0);
+            mKeyGestureEventListenerRecords.remove(pid);
+        }
+    }
+
+    private void onKeyGestureEventListenerDied(int pid) {
+        synchronized (mKeyGestureEventListenerRecords) {
+            mKeyGestureEventListenerRecords.remove(pid);
+        }
+    }
+
+    // A record of a registered key gesture event listener from one process.
+    private class KeyGestureEventListenerRecord implements IBinder.DeathRecipient {
+        public final int mPid;
+        public final IKeyGestureEventListener mListener;
+
+        KeyGestureEventListenerRecord(int pid, IKeyGestureEventListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Key gesture event listener for pid " + mPid + " died.");
+            }
+            onKeyGestureEventListenerDied(mPid);
+        }
+
+        public void onKeyGestureEvent(KeyGestureEvent event) {
+            try {
+                mListener.onKeyGestureEvent(event.getDeviceId(), event.getKeycodes(),
+                        event.getModifierState(), event.getKeyGestureType());
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid
+                        + " that key gesture event occurred, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 3d2f951..1daf4db 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -24,9 +24,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.input.KeyGestureEvent;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
-import android.hardware.input.KeyboardSystemShortcut;
 import android.icu.util.ULocale;
 import android.text.TextUtils;
 import android.util.Log;
@@ -66,14 +66,17 @@
      * defined in "stats/atoms/input/input_extension_atoms.proto"
      */
     public static void logKeyboardSystemsEventReportedAtom(@NonNull InputDevice inputDevice,
-            @NonNull KeyboardSystemShortcut keyboardSystemShortcut) {
+            @NonNull KeyGestureEvent keyGestureEvent) {
+        if (inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return;
+        }
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                 inputDevice.getVendorId(), inputDevice.getProductId(),
-                keyboardSystemShortcut.getSystemShortcut(), keyboardSystemShortcut.getKeycodes(),
-                keyboardSystemShortcut.getModifierState(), inputDevice.getDeviceBus());
+                keyGestureEvent.getKeyGestureType(), keyGestureEvent.getKeycodes(),
+                keyGestureEvent.getModifierState(), inputDevice.getDeviceBus());
 
         if (DEBUG) {
-            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemShortcut);
+            Slog.d(TAG, "Logging Keyboard system event: " + keyGestureEvent);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
deleted file mode 100644
index 092058e..0000000
--- a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 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.input;
-
-import android.annotation.BinderThread;
-import android.hardware.input.IKeyboardSystemShortcutListener;
-import android.hardware.input.KeyboardSystemShortcut;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
-/**
- * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
- * keyboard shortcut is triggered.
- */
-final class KeyboardShortcutCallbackHandler {
-
-    private static final String TAG = "KeyboardShortcut";
-
-    // To enable these logs, run:
-    // 'adb shell setprop log.tag.KeyboardShortcutCallbackHandler DEBUG' (requires restart)
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    // List of currently registered keyboard system shortcut listeners keyed by process pid
-    @GuardedBy("mKeyboardSystemShortcutListenerRecords")
-    private final SparseArray<KeyboardSystemShortcutListenerRecord>
-            mKeyboardSystemShortcutListenerRecords = new SparseArray<>();
-
-    public void onKeyboardSystemShortcutTriggered(int deviceId,
-            KeyboardSystemShortcut systemShortcut) {
-        if (DEBUG) {
-            Slog.d(TAG, "Keyboard system shortcut triggered, deviceId = " + deviceId
-                    + ", systemShortcut = " + systemShortcut);
-        }
-
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            for (int i = 0; i < mKeyboardSystemShortcutListenerRecords.size(); i++) {
-                mKeyboardSystemShortcutListenerRecords.valueAt(i).onKeyboardSystemShortcutTriggered(
-                        deviceId, systemShortcut);
-            }
-        }
-    }
-
-    /** Register the keyboard system shortcut listener for a process. */
-    @BinderThread
-    public void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
-            int pid) {
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            if (mKeyboardSystemShortcutListenerRecords.get(pid) != null) {
-                throw new IllegalStateException("The calling process has already registered "
-                        + "a KeyboardSystemShortcutListener.");
-            }
-            KeyboardSystemShortcutListenerRecord record = new KeyboardSystemShortcutListenerRecord(
-                    pid, listener);
-            try {
-                listener.asBinder().linkToDeath(record, 0);
-            } catch (RemoteException ex) {
-                throw new RuntimeException(ex);
-            }
-            mKeyboardSystemShortcutListenerRecords.put(pid, record);
-        }
-    }
-
-    /** Unregister the keyboard system shortcut listener for a process. */
-    @BinderThread
-    public void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
-            int pid) {
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            KeyboardSystemShortcutListenerRecord record =
-                    mKeyboardSystemShortcutListenerRecords.get(pid);
-            if (record == null) {
-                throw new IllegalStateException("The calling process has no registered "
-                        + "KeyboardSystemShortcutListener.");
-            }
-            if (record.mListener.asBinder() != listener.asBinder()) {
-                throw new IllegalStateException("The calling process has a different registered "
-                        + "KeyboardSystemShortcutListener.");
-            }
-            record.mListener.asBinder().unlinkToDeath(record, 0);
-            mKeyboardSystemShortcutListenerRecords.remove(pid);
-        }
-    }
-
-    private void onKeyboardSystemShortcutListenerDied(int pid) {
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            mKeyboardSystemShortcutListenerRecords.remove(pid);
-        }
-    }
-
-    // A record of a registered keyboard system shortcut listener from one process.
-    private class KeyboardSystemShortcutListenerRecord implements IBinder.DeathRecipient {
-        public final int mPid;
-        public final IKeyboardSystemShortcutListener mListener;
-
-        KeyboardSystemShortcutListenerRecord(int pid, IKeyboardSystemShortcutListener listener) {
-            mPid = pid;
-            mListener = listener;
-        }
-
-        @Override
-        public void binderDied() {
-            if (DEBUG) {
-                Slog.d(TAG, "Keyboard system shortcut listener for pid " + mPid + " died.");
-            }
-            onKeyboardSystemShortcutListenerDied(mPid);
-        }
-
-        public void onKeyboardSystemShortcutTriggered(int deviceId, KeyboardSystemShortcut data) {
-            try {
-                mListener.onKeyboardSystemShortcutTriggered(deviceId, data.getKeycodes(),
-                        data.getModifierState(), data.getSystemShortcut());
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify process " + mPid
-                        + " that keyboard system shortcut was triggered, assuming it died.", ex);
-                binderDied();
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 9818916..abb2132 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -489,6 +489,7 @@
 
                 } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
                     hasValidSound = false;
+                    hasValidVibrate = false;
                 }
             }
         }
@@ -753,6 +754,13 @@
         // notifying app does not have the VIBRATE permission.
         final long identity = Binder.clearCallingIdentity();
         try {
+            // Need to explicitly cancel a previously playing vibration
+            // Otherwise a looping vibration will not be stopped when starting a new one.
+            if (mVibrateNotificationKey != null
+                    && !mVibrateNotificationKey.equals(record.getKey())) {
+                mVibrateNotificationKey = null;
+                mVibratorHelper.cancelVibration();
+            }
             final float scale = getVibrationIntensity(record);
             final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0
                     ? mVibratorHelper.scale(effect, scale) : effect;
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 5a45186..7ed8972 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -29,8 +29,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
-import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -476,7 +475,7 @@
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
                             + " category=" + category + " role=" + role);
                 }
-                notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(intent));
+                notifyKeyGestureCompleted(keyEvent, getKeyGestureTypeFromIntent(intent));
                 return true;
             } else {
                 return false;
@@ -497,19 +496,19 @@
                         + "the activity to which it is registered was not found: "
                         + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
             }
-            notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(shortcutIntent));
+            notifyKeyGestureCompleted(keyEvent, getKeyGestureTypeFromIntent(shortcutIntent));
             return true;
         }
         return false;
     }
 
-    private void notifyKeyboardShortcutTriggered(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
-        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+    private void notifyKeyGestureCompleted(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
+        if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
             return;
         }
-        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
-                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
+        mInputManagerInternal.notifyKeyGestureCompleted(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), gestureType);
     }
 
     /**
@@ -710,21 +709,21 @@
 
 
     /**
-     * Find Keyboard shortcut event corresponding to intent filter category. Returns
-     * {@code SYSTEM_SHORTCUT_UNSPECIFIED if no matching event found}
+     * Find Key gesture type corresponding to intent filter category. Returns
+     * {@code KEY_GESTURE_TYPE_UNSPECIFIED if no matching event found}
      */
-    @KeyboardSystemShortcut.SystemShortcut
-    private static int getSystemShortcutFromIntent(Intent intent) {
+    @KeyGestureEvent.KeyGestureType
+    private static int getKeyGestureTypeFromIntent(Intent intent) {
         Intent selectorIntent = intent.getSelector();
         if (selectorIntent != null) {
             Set<String> selectorCategories = selectorIntent.getCategories();
             if (selectorCategories != null && !selectorCategories.isEmpty()) {
                 for (String intentCategory : selectorCategories) {
-                    int systemShortcut = getEventFromSelectorCategory(intentCategory);
-                    if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+                    int keyGestureType = getKeyGestureTypeFromSelectorCategory(intentCategory);
+                    if (keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
                         continue;
                     }
-                    return systemShortcut;
+                    return keyGestureType;
                 }
             }
         }
@@ -733,69 +732,68 @@
         // so check for that.
         String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
         if (!TextUtils.isEmpty(role)) {
-            return getLogEventFromRole(role);
+            return getKeyGestureTypeFromRole(role);
         }
 
         Set<String> intentCategories = intent.getCategories();
         if (intentCategories == null || intentCategories.isEmpty()
                 || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
         if (intent.getComponent() == null) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
 
         // TODO(b/280423320): Add new field package name associated in the
         //  KeyboardShortcutEvent atom and log it accordingly.
-        return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+        return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
     }
 
-    @KeyboardSystemShortcut.SystemShortcut
-    private static int getEventFromSelectorCategory(String category) {
+    @KeyGestureEvent.KeyGestureType
+    private static int getKeyGestureTypeFromSelectorCategory(String category) {
         switch (category) {
             case Intent.CATEGORY_APP_BROWSER:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER;
             case Intent.CATEGORY_APP_EMAIL:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL;
             case Intent.CATEGORY_APP_CONTACTS:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS;
             case Intent.CATEGORY_APP_CALENDAR:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR;
             case Intent.CATEGORY_APP_CALCULATOR:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR;
             case Intent.CATEGORY_APP_MUSIC:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC;
             case Intent.CATEGORY_APP_MAPS:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS;
             case Intent.CATEGORY_APP_MESSAGING:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING;
             case Intent.CATEGORY_APP_GALLERY:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY;
             case Intent.CATEGORY_APP_FILES:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES;
             case Intent.CATEGORY_APP_WEATHER:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER;
             case Intent.CATEGORY_APP_FITNESS:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS;
             default:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
     }
 
     /**
-     * Find KeyboardLogEvent corresponding to the provide system role name.
-     * Returns {@code null} if no matching event found.
+     * Find KeyGestureType corresponding to the provide system role name.
+     * Returns {@code KEY_GESTURE_TYPE_UNSPECIFIED} if no matching event found.
      */
-    @KeyboardSystemShortcut.SystemShortcut
-    private static int getLogEventFromRole(String role) {
+    @KeyGestureEvent.KeyGestureType
+    private static int getKeyGestureTypeFromRole(String role) {
         if (RoleManager.ROLE_BROWSER.equals(role)) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER;
         } else if (RoleManager.ROLE_SMS.equals(role)) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING;
         } else {
-            Log.w(TAG, "Keyboard shortcut to launch "
-                    + role + " not supported for logging");
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+            Log.w(TAG, "Keyboard gesture event to launch " + role + " not supported for logging");
+            return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 720c1c2..aa56e8d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,7 +139,7 @@
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
 import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -1819,7 +1819,7 @@
     }
 
     private void handleShortPressOnHome(KeyEvent event) {
-        notifyKeyboardShortcutTriggered(event, KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME);
+        notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME);
 
         // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
         final HdmiControl hdmiControl = getHdmiControl();
@@ -2053,8 +2053,8 @@
             }
             switch (mDoubleTapOnHomeBehavior) {
                 case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH);
                     mHomeConsumed = true;
                     toggleRecentApps();
                     break;
@@ -2082,23 +2082,23 @@
                 case LONG_PRESS_HOME_ALL_APPS:
                     if (mHasFeatureLeanback) {
                         launchAllAppsAction();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
                     }
                     break;
                 case LONG_PRESS_HOME_ASSIST:
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
                     launchAssistAction(null, event.getDeviceId(), event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
                     break;
                 case LONG_PRESS_HOME_NOTIFICATION_PANEL:
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                     toggleNotificationPanel();
                     break;
                 default:
@@ -3285,29 +3285,29 @@
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
 
-    private void notifyKeyboardShortcutTriggeredOnActionUp(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+    private void notifyKeyGestureCompletedOnActionUp(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
         if (event.getAction() != KeyEvent.ACTION_UP) {
             return;
         }
-        notifyKeyboardShortcutTriggered(event, systemShortcut);
+        notifyKeyGestureCompleted(event, gestureType);
     }
 
-    private void notifyKeyboardShortcutTriggeredOnActionDown(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+    private void notifyKeyGestureCompletedOnActionDown(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return;
         }
-        notifyKeyboardShortcutTriggered(event, systemShortcut);
+        notifyKeyGestureCompleted(event, gestureType);
     }
 
-    private void notifyKeyboardShortcutTriggered(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
-        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+    private void notifyKeyGestureCompleted(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
+        if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
             return;
         }
-        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
-                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
+        mInputManagerInternal.notifyKeyGestureCompleted(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), gestureType);
     }
 
     @Override
@@ -3417,8 +3417,8 @@
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3427,8 +3427,8 @@
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH);
                     }
                 }
                 return true;
@@ -3437,8 +3437,8 @@
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
                             deviceId, event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
                     return true;
                 }
                 break;
@@ -3451,16 +3451,16 @@
             case KeyEvent.KEYCODE_I:
                 if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_L:
                 if (firstDown && event.isMetaPressed()) {
                     lockNow(null /* options */);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN);
                     return true;
                 }
                 break;
@@ -3468,12 +3468,12 @@
                 if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES);
                     } else {
                         toggleNotificationPanel();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                     }
                     return true;
                 }
@@ -3481,8 +3481,8 @@
             case KeyEvent.KEYCODE_S:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT);
                     return true;
                 }
                 break;
@@ -3495,16 +3495,16 @@
                         } catch (RemoteException e) {
                             Slog.d(TAG, "Error taking bugreport", e);
                         }
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT);
                         return true;
                     }
                 }
                 // fall through
             case KeyEvent.KEYCODE_ESCAPE:
                 if (firstDown && event.isMetaPressed()) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
                     injectBackGesture(event.getDownTime());
                     return true;
                 }
@@ -3513,8 +3513,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event));
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION);
                         return true;
                     }
                 }
@@ -3524,8 +3524,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event));
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE);
                         return true;
                     }
                 }
@@ -3535,15 +3535,15 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 true /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION);
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(true /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS);
                     } else {
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
                         injectBackGesture(event.getDownTime());
                     }
                     return true;
@@ -3554,13 +3554,13 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 false /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION);
                         return true;
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(false /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS);
                         return true;
                     }
                 }
@@ -3568,8 +3568,8 @@
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER);
                     return true;
                 }
                 break;
@@ -3622,31 +3622,31 @@
                     intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
 
-                    int systemShortcut = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
-                            ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN
-                            : KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP;
-                    notifyKeyboardShortcutTriggered(event, systemShortcut);
+                    int gestureType = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
+                            ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN
+                            : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP;
+                    notifyKeyGestureCompleted(event, gestureType);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
                 if (!down) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE);
                 }
                 return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
@@ -3673,8 +3673,8 @@
                 if (firstDown && !keyguardOn && isUserSetupComplete()) {
                     if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                         return true;
                     } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
@@ -3683,8 +3683,8 @@
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            notifyKeyboardShortcutTriggered(event,
-                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                             return true;
                         }
                     }
@@ -3697,20 +3697,20 @@
                         Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
                         msg.setAsynchronous(true);
                         msg.sendToTarget();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
                     }
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -3718,8 +3718,8 @@
                     switch (mSearchKeyBehavior) {
                         case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            notifyKeyboardShortcutTriggered(event,
-                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH);
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH);
                             return true;
                         }
                         case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH:
@@ -3732,8 +3732,8 @@
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, focusedToken, direction);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH);
                     return true;
                 }
                 break;
@@ -3752,13 +3752,13 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
                             launchAllAppsViaA11y();
-                            notifyKeyboardShortcutTriggered(event,
-                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
@@ -3786,16 +3786,16 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                         return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_CAPS_LOCK:
                 if (!down) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                 }
                 break;
             case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
@@ -3809,12 +3809,12 @@
                 if (firstDown) {
                     if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
                         toggleNotificationPanel();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                     } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
                         showSystemSettings();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS);
                     }
                 }
                 return true;
@@ -4760,8 +4760,8 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
                 if (down) {
                     // There may have other embedded activities on the same Task. Try to move the
                     // focus before processing the back event.
@@ -4782,12 +4782,12 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                int systemShortcut = keyCode == KEYCODE_VOLUME_DOWN
-                        ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN
+                int gestureType = keyCode == KEYCODE_VOLUME_DOWN
+                        ? KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN
                         : keyCode == KEYCODE_VOLUME_UP
-                                ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP
-                                : KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE;
-                notifyKeyboardShortcutTriggeredOnActionDown(event, systemShortcut);
+                                ? KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP
+                                : KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE;
+                notifyKeyGestureCompletedOnActionDown(event, gestureType);
                 if (down) {
                     sendSystemKeyToStatusBarAsync(event);
 
@@ -4888,8 +4888,8 @@
             }
 
             case KeyEvent.KEYCODE_TV_POWER: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
                 if (down && hdmiControlManager != null) {
@@ -4899,8 +4899,8 @@
             }
 
             case KeyEvent.KEYCODE_POWER: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER);
                 EventLogTags.writeInterceptPower(
                         KeyEvent.actionToString(event.getAction()),
                         mPowerKeyHandled ? 1 : 0,
@@ -4923,16 +4923,16 @@
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
                 // fall through
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION);
                 result &= ~ACTION_PASS_TO_USER;
                 interceptSystemNavigationKey(event);
                 break;
             }
 
             case KeyEvent.KEYCODE_SLEEP: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!mPowerManager.isInteractive()) {
@@ -4948,8 +4948,8 @@
             }
 
             case KeyEvent.KEYCODE_SOFT_SLEEP: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!down) {
@@ -4960,8 +4960,8 @@
             }
 
             case KeyEvent.KEYCODE_WAKEUP: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = true;
                 break;
@@ -4970,8 +4970,8 @@
             case KeyEvent.KEYCODE_MUTE:
                 result &= ~ACTION_PASS_TO_USER;
                 if (down && event.getRepeatCount() == 0) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE);
                     toggleMicrophoneMuteFromKey();
                 }
                 break;
@@ -4986,8 +4986,8 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY);
                 if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
                     // If the global session is active pass all media keys to it
                     // instead of the active window.
@@ -5032,8 +5032,8 @@
                             0 /* unused */, event.getEventTime() /* eventTime */);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -5044,8 +5044,8 @@
                     Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 72594b3..2e8a0c6 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -433,7 +433,8 @@
     boolean updateLocked(float screenBrightnessOverride, CharSequence overrideTag,
             boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
             @Display.StateReason int dozeScreenStateReason,
-            float dozeScreenBrightness, boolean overrideDrawWakeLock,
+            float dozeScreenBrightness, boolean useNormalBrightnessForDoze,
+            boolean overrideDrawWakeLock,
             PowerSaveState powerSaverState, boolean quiescent,
             boolean dozeAfterScreenOff, boolean bootCompleted,
             boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
@@ -461,11 +462,13 @@
                 }
             }
             mDisplayPowerRequest.dozeScreenBrightness = dozeScreenBrightness;
+            mDisplayPowerRequest.useNormalBrightnessForDoze = useNormalBrightnessForDoze;
         } else {
             mDisplayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
             mDisplayPowerRequest.dozeScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             mDisplayPowerRequest.dozeScreenStateReason =
                     Display.STATE_REASON_DEFAULT_POLICY;
+            mDisplayPowerRequest.useNormalBrightnessForDoze = false;
         }
         mDisplayPowerRequest.lowPowerMode = powerSaverState.batterySaverEnabled;
         mDisplayPowerRequest.screenLowPowerBrightnessFactor = powerSaverState.brightnessFactor;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 699c9b5..b22b726 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -662,6 +662,7 @@
      */
     private float mDozeScreenBrightnessOverrideFromDreamManagerFloat =
             PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    private boolean mUseNormalBrightnessForDoze;
 
     // Keep display state when dozing.
     private boolean mDrawWakeLockOverrideFromSidekick;
@@ -3656,6 +3657,7 @@
                         mDozeScreenStateOverrideFromDreamManager,
                         mDozeScreenStateOverrideReasonFromDreamManager,
                         mDozeScreenBrightnessOverrideFromDreamManagerFloat,
+                        mUseNormalBrightnessForDoze,
                         mDrawWakeLockOverrideFromSidekick,
                         mBatterySaverSupported
                                 ?
@@ -4470,13 +4472,13 @@
 
     private void setDozeOverrideFromDreamManagerInternal(
             int screenState, @Display.StateReason int reason, float screenBrightnessFloat,
-            int screenBrightnessInt) {
+            int screenBrightnessInt, boolean useNormalBrightnessForDoze) {
         synchronized (mLock) {
             if (mDozeScreenStateOverrideFromDreamManager != screenState
                     || mDozeScreenBrightnessOverrideFromDreamManager != screenBrightnessInt
                     || !BrightnessSynchronizer.floatEquals(
-                    mDozeScreenBrightnessOverrideFromDreamManagerFloat,
-                    screenBrightnessFloat)) {
+                    mDozeScreenBrightnessOverrideFromDreamManagerFloat, screenBrightnessFloat)
+                    || mUseNormalBrightnessForDoze != useNormalBrightnessForDoze) {
                 mDozeScreenStateOverrideFromDreamManager = screenState;
                 mDozeScreenStateOverrideReasonFromDreamManager = reason;
                 mDozeScreenBrightnessOverrideFromDreamManager = screenBrightnessInt;
@@ -4484,6 +4486,7 @@
                         isValidBrightnessValue(screenBrightnessFloat)
                                 ? screenBrightnessFloat
                                 : BrightnessSynchronizer.brightnessIntToFloat(screenBrightnessInt);
+                mUseNormalBrightnessForDoze = useNormalBrightnessForDoze;
                 mDirty |= DIRTY_SETTINGS;
                 updatePowerStateLocked();
             }
@@ -4798,6 +4801,7 @@
             pw.println("  mDrawWakeLockOverrideFromSidekick=" + mDrawWakeLockOverrideFromSidekick);
             pw.println("  mDozeScreenBrightnessOverrideFromDreamManager="
                     + mDozeScreenBrightnessOverrideFromDreamManager);
+            pw.println("  mUseNormalBrightnessForDoze=" + mUseNormalBrightnessForDoze);
             pw.println("  mScreenBrightnessMinimum=" + mScreenBrightnessMinimum);
             pw.println("  mScreenBrightnessMaximum=" + mScreenBrightnessMaximum);
             pw.println("  mScreenBrightnessDefault=" + mScreenBrightnessDefault);
@@ -7115,7 +7119,8 @@
 
         @Override
         public void setDozeOverrideFromDreamManager(
-                int screenState, int reason, float screenBrightnessFloat, int screenBrightnessInt) {
+                int screenState, int reason, float screenBrightnessFloat, int screenBrightnessInt,
+                boolean useNormalBrightnessForDoze) {
             switch (screenState) {
                 case Display.STATE_UNKNOWN:
                 case Display.STATE_OFF:
@@ -7138,7 +7143,7 @@
                 screenBrightnessFloat = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
             setDozeOverrideFromDreamManagerInternal(screenState, reason, screenBrightnessFloat,
-                    screenBrightnessInt);
+                    screenBrightnessInt, useNormalBrightnessForDoze);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 68eb8eb..480db25 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -76,8 +76,6 @@
     // Keep this in sync with the definitions in TraceService
     private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
             "com.android.traceur.NOTIFY_SESSION_STOPPED";
-    private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN =
-            "com.android.traceur.NOTIFY_SESSION_STOLEN";
 
     private static final int REPORT_BEGIN =
             TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN;
@@ -97,13 +95,12 @@
 
     private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
         /**
-         * Notifies system tracing app that a tracing session has ended. If a session is repurposed
-         * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
-         * there is no buffer available to dump.
+         * Notifies system tracing app that a tracing session has ended. sessionStolen is ignored,
+         * as trace sessions are no longer stolen and are always cloned instead.
          */
         @Override
-        public void notifyTraceSessionEnded(boolean sessionStolen) {
-            TracingServiceProxy.this.notifyTraceur(sessionStolen);
+        public void notifyTraceSessionEnded(boolean sessionStolen /* unused */) {
+            TracingServiceProxy.this.notifyTraceur();
         }
 
         @Override
@@ -132,7 +129,7 @@
         }
     }
 
-    private void notifyTraceur(boolean sessionStolen) {
+    private void notifyTraceur() {
         final Intent intent = new Intent();
 
         try {
@@ -141,11 +138,7 @@
                     PackageManager.MATCH_SYSTEM_ONLY);
 
             intent.setClassName(info.packageName, TRACING_APP_ACTIVITY);
-            if (sessionStolen) {
-                intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOLEN);
-            } else {
-                intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED);
-            }
+            intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED);
 
             final long identity = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 0c10551..e27b2be 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1156,7 +1156,7 @@
                 }
 
                 if (rootTask.inFreeformWindowingMode()) {
-                    rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                    rootTask.setRootTaskWindowingMode(WINDOWING_MODE_FULLSCREEN);
                     rootTask.setBounds(null);
                 } else if (!r.supportsFreeform()) {
                     throw new IllegalStateException(
@@ -1165,9 +1165,9 @@
                     // If the window is on a freeform display, set it to undefined. It will be
                     // resolved to freeform and it can adjust windowing mode when the display mode
                     // changes in runtime.
-                    rootTask.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+                    rootTask.setRootTaskWindowingMode(WINDOWING_MODE_UNDEFINED);
                 } else {
-                    rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+                    rootTask.setRootTaskWindowingMode(WINDOWING_MODE_FREEFORM);
                 }
             }
         } finally {
@@ -1278,7 +1278,7 @@
         if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
             final int restoreWindowingMode = requester.getRequestedOverrideWindowingMode();
             targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
-            requester.setWindowingMode(targetWindowingMode);
+            requester.setRootTaskWindowingMode(targetWindowingMode);
             // The restore windowing mode must be set after the windowing mode is set since
             // Task#setWindowingMode resets the restore windowing mode to WINDOWING_MODE_INVALID.
             requester.mMultiWindowRestoreWindowingMode = restoreWindowingMode;
@@ -1297,9 +1297,8 @@
     public void startLockTaskModeByToken(IBinder token) {
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            if (r != null) {
-                mService.startLockTaskMode(r.getTask(), false /* isSystemCaller */);
-            }
+            if (r == null) return;
+            mService.startLockTaskMode(r.getTask(), false /* isSystemCaller */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a76f1b6..3ac91b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -789,8 +789,6 @@
      */
     private boolean mWillCloseOrEnterPip;
 
-    final LetterboxUiController mLetterboxUiController;
-
     /**
      * App Compat Facade
      */
@@ -1324,7 +1322,7 @@
             pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true");
         }
 
-        mLetterboxUiController.dump(pw, prefix);
+        mAppCompatController.dump(pw, prefix);
     }
 
     static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r,
@@ -1988,12 +1986,10 @@
         }
 
         // Don't move below setActivityType since it triggers onConfigurationChange ->
-        // resolveOverrideConfiguration that requires having mLetterboxUiController initialised.
+        // resolveOverrideConfiguration that requires having mAppCompatController initialised.
         // Don't move below setOrientation(info.screenOrientation) since it triggers
-        // getOverrideOrientation that requires having mLetterboxUiController
-        // initialised.
+        // getOverrideOrientation that requires having mAppCompatController initialised.
         mAppCompatController = new AppCompatController(mWmService, this);
-        mLetterboxUiController = new LetterboxUiController(mWmService, this);
         mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
         if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
             // When the stable configuration is the default behavior, override for the legacy apps
@@ -2016,7 +2012,7 @@
         mIsUserAlwaysVisible =  properties != null && properties.getAlwaysVisible();
 
         mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0 || mIsUserAlwaysVisible;
-        setOrientation(info.screenOrientation);
+        setOverrideOrientation(info.screenOrientation);
         mRotationAnimationHint = info.rotationAnimation;
 
         mShowWhenLocked = (aInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0;
@@ -3626,10 +3622,10 @@
             final WindowContainer<?> trigger = endTask ? task : this;
             final Transition newTransition =
                     mTransitionController.requestCloseTransitionIfNeeded(trigger);
-            if (newTransition != null) {
-                newTransition.collectClose(trigger);
-            } else if (mTransitionController.isCollecting()) {
-                mTransitionController.getCollectingTransition().collectClose(trigger);
+            final Transition transition = newTransition != null
+                    ? newTransition : mTransitionController.getCollectingTransition();
+            if (transition != null) {
+                transition.collectClose(trigger);
             }
             if (isState(RESUMED)) {
                 if (endTask) {
@@ -4302,9 +4298,9 @@
         // closing the task.
         final WindowContainer trigger = remove && task != null && task.getChildCount() == 1
                 ? task : this;
-        final Transition newTransit = mTransitionController.requestCloseTransitionIfNeeded(trigger);
-        if (newTransit != null) {
-            newTransit.collectClose(trigger);
+        final Transition tr = mTransitionController.requestCloseTransitionIfNeeded(trigger);
+        if (tr != null) {
+            tr.collectClose(trigger);
         } else if (mTransitionController.isCollecting()) {
             mTransitionController.getCollectingTransition().collectClose(trigger);
         }
@@ -5320,12 +5316,14 @@
         task.lastDescription = description;
     }
 
-    void setDeferHidingClient(boolean deferHidingClient) {
-        if (mDeferHidingClient == deferHidingClient) {
-            return;
-        }
-        mDeferHidingClient = deferHidingClient;
-        if (!mDeferHidingClient && !mVisibleRequested) {
+    void setDeferHidingClient() {
+        mDeferHidingClient = true;
+    }
+
+    void clearDeferHidingClient() {
+        if (!mDeferHidingClient) return;
+        mDeferHidingClient = false;
+        if (!mVisibleRequested) {
             // Hiding the client is no longer deferred and the app isn't visible still, go ahead and
             // update the visibility.
             setVisibility(false);
@@ -5449,8 +5447,8 @@
         boolean isCollecting = false;
         boolean inFinishingTransition = false;
         if (mTransitionController.isShellTransitionsEnabled()) {
-            isCollecting = mTransitionController.isCollecting();
-            if (isCollecting) {
+            if (mTransitionController.isCollecting()) {
+                isCollecting = true;
                 mTransitionController.collect(this);
             } else {
                 // Failsafe to make sure that we show any activities that were incorrectly hidden
@@ -6195,7 +6193,11 @@
             // stopped or stopping. This gives it a chance to enter Pip in onPause().
             final boolean deferHidingClient = canEnterPictureInPicture
                     && !isState(STARTED, STOPPING, STOPPED, PAUSED);
-            setDeferHidingClient(deferHidingClient);
+            if (deferHidingClient) {
+                setDeferHidingClient();
+            } else {
+                clearDeferHidingClient();
+            }
             setVisibility(false);
 
             switch (getState()) {
@@ -6234,7 +6236,7 @@
                 Slog.v(TAG_VISIBILITY, "Resume visible activity, " + this);
             }
             return getRootTask().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
-                    null /* options */);
+                    null /* options */, false /* skipPause */);
         } else if (shouldPauseActivity(activeActivity)) {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Pause visible activity, " + this);
@@ -6524,9 +6526,7 @@
     void stopIfPossible() {
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);
         if (finishing) {
-            Slog.e(TAG, "Request to stop a finishing activity: " + this);
-            destroyIfPossible("stopIfPossible-finishing");
-            return;
+            throw new IllegalStateException("Request to stop a finishing activity: " + this);
         }
         if (isNoHistory()) {
             if (!task.shouldSleepActivities()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index a0ef030..1660ca9 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -612,11 +612,10 @@
         final Task task = r.getTask();
         mService.deferWindowLayout();
         try {
-            final TransitionController controller = r.mTransitionController;
-            final Transition transition = controller.getCollectingTransition();
+            final Transition transition = r.mTransitionController.getCollectingTransition();
             if (transition != null) {
                 transition.setRemoteAnimationApp(r.app.getThread());
-                controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
+                transition.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
             }
             task.moveToFront("startExistingRecents");
             task.mInResumeTopActivity = true;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 18aa9a0..6479111 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1355,9 +1355,21 @@
             mService.resumeAppSwitches();
         }
 
+        // Only do the create here since startActivityInner can abort. If it doesn't abort,
+        // the requestStart will be sent in handleStartRequest.
+        final Transition newTransition = r.mTransitionController.isShellTransitionsEnabled()
+                ? r.mTransitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
+        // Because startActivity must run immediately, it can get combined with another
+        // transition meaning it is no-longer independent. This is NOT desirable, but is the
+        // only option for the time being.
+        final boolean isIndependent = newTransition != null;
+        final Transition transition = isIndependent ? newTransition
+                : mService.getTransitionController().getCollectingTransition();
+
         mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                 request.voiceInteractor, startFlags, checkedOptions,
-                inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid);
+                inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid, transition,
+                isIndependent);
 
         if (request.outActivity != null) {
             request.outActivity[0] = mLastStartActivityRecord;
@@ -1509,33 +1521,27 @@
             int startFlags, ActivityOptions options, Task inTask,
             TaskFragment inTaskFragment,
             BalVerdict balVerdict,
-            NeededUriGrants intentGrants, int realCallingUid) {
+            NeededUriGrants intentGrants, int realCallingUid, Transition transition,
+            boolean isIndependentLaunch) {
         int result = START_CANCELED;
         final Task startedActivityRootTask;
 
-        // Create a transition now to record the original intent of actions taken within
-        // startActivityInner. Otherwise, logic in startActivityInner could start a different
-        // transition based on a sub-action.
-        // Only do the create here (and defer requestStart) since startActivityInner might abort.
-        final TransitionController transitionController = r.mTransitionController;
-        Transition newTransition = transitionController.isShellTransitionsEnabled()
-                ? transitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
         RemoteTransition remoteTransition = r.takeRemoteTransition();
         // Create a display snapshot as soon as possible.
-        if (newTransition != null && mRequest.freezeScreen) {
+        if (isIndependentLaunch && mRequest.freezeScreen) {
             final TaskDisplayArea tda = mLaunchParams.hasPreferredTaskDisplayArea()
                     ? mLaunchParams.mPreferredTaskDisplayArea
                     : mRootWindowContainer.getDefaultTaskDisplayArea();
             final DisplayContent dc = mRootWindowContainer.getDisplayContentOrCreate(
                     tda.getDisplayId());
             if (dc != null) {
-                transitionController.collect(dc);
-                transitionController.collectVisibleChange(dc);
+                transition.collect(dc);
+                transition.collectVisibleChange(dc);
             }
         }
         try {
             mService.deferWindowLayout();
-            transitionController.collect(r);
+            r.mTransitionController.collect(r);
             try {
                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
                 result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
@@ -1545,8 +1551,8 @@
                 Slog.e(TAG, "Exception on startActivityInner", ex);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-                startedActivityRootTask = handleStartResult(r, options, result, newTransition,
-                        remoteTransition);
+                startedActivityRootTask = handleStartResult(r, options, result, isIndependentLaunch,
+                        remoteTransition, transition);
             }
         } finally {
             mService.continueWindowLayout();
@@ -1571,8 +1577,8 @@
      * @return the root task where the successful started activity resides.
      */
     private @Nullable Task handleStartResult(@NonNull ActivityRecord started,
-            ActivityOptions options, int result, Transition newTransition,
-            RemoteTransition remoteTransition) {
+            ActivityOptions options, int result, boolean isIndependentLaunch,
+            RemoteTransition remoteTransition, @NonNull Transition transition) {
         final boolean userLeaving = mSupervisor.mUserLeaving;
         mSupervisor.mUserLeaving = false;
         final Task currentRootTask = started.getRootTask();
@@ -1596,8 +1602,9 @@
                     && !startedActivityRootTask.mCreatedByOrganizer) {
                 startedActivityRootTask.removeIfPossible("handleStartResult");
             }
-            if (newTransition != null) {
-                newTransition.abort();
+            if (isIndependentLaunch
+                    && mService.getTransitionController().isShellTransitionsEnabled()) {
+                transition.abort();
             }
             return null;
         }
@@ -1649,44 +1656,46 @@
             // The activity is started new rather than just brought forward, so record it as an
             // existence change.
             transitionController.collectExistenceChange(started);
-        } else if (result == START_DELIVERED_TO_TOP && newTransition != null
+        } else if (result == START_DELIVERED_TO_TOP && isIndependentLaunch
                 // An activity has changed order/visibility or the task is occluded by a transient
                 // activity, so this isn't just deliver-to-top
                 && mMovedToTopActivity == null
                 && !transitionController.hasOrderChanges()
                 && !transitionController.isTransientHide(startedActivityRootTask)
-                && !newTransition.hasChanged(mLastStartActivityRecord)) {
+                && !transition.hasChanged(mLastStartActivityRecord)) {
             // We just delivered to top, so there isn't an actual transition here.
             if (!forceTransientTransition) {
-                newTransition.abort();
-                newTransition = null;
+                transition.abort();
+                transition = null;
             }
         }
-        if (forceTransientTransition) {
-            transitionController.collect(mLastStartActivityRecord);
-            transitionController.collect(mPriorAboveTask);
+        if (forceTransientTransition && transition != null) {
+            transition.collect(mLastStartActivityRecord);
+            transition.collect(mPriorAboveTask);
             // If keyguard is active and occluded, the transient target won't be moved to front
             // to be collected, so set transient again after it is collected.
-            transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
+            transition.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
             final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
             // update wallpaper target to TransientHide
             dc.mWallpaperController.adjustWallpaperWindows();
             // execute transition because there is no change
-            transitionController.setReady(dc, true /* ready */);
+            transition.setReady(dc, true /* ready */);
         }
-        if (!userLeaving) {
+        if (!userLeaving && transition != null) {
             // no-user-leaving implies not entering PiP.
-            transitionController.setCanPipOnFinish(false /* canPipOnFinish */);
+            transition.setCanPipOnFinish(false /* canPipOnFinish */);
         }
-        if (newTransition != null) {
-            transitionController.requestStartTransition(newTransition,
+        if (isIndependentLaunch && transition != null) {
+            transitionController.requestStartTransition(transition,
                     mTargetTask == null ? started.getTask() : mTargetTask,
                     remoteTransition, null /* displayChange */);
         } else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) {
             // Do nothing if the activity is started and is resumed directly.
         } else if (isStarted) {
             // Make the collecting transition wait until this request is ready.
-            transitionController.setReady(started, false);
+            if (transition != null) {
+                transition.setReady(started, false);
+            }
         }
         return startedActivityRootTask;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f23211b..121ab2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2116,12 +2116,7 @@
                     Slog.w(TAG, "removeTask: No task remove with id=" + taskId);
                     return false;
                 }
-
-                if (task.isLeafTask()) {
-                    mTaskSupervisor.removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task");
-                } else {
-                    mTaskSupervisor.removeRootTask(task);
-                }
+                removeTask(task);
                 return true;
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -2129,6 +2124,14 @@
         }
     }
 
+    void removeTask(@NonNull Task task) {
+        if (task.isLeafTask()) {
+            mTaskSupervisor.removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task");
+        } else {
+            mTaskSupervisor.removeRootTask(task);
+        }
+    }
+
     @Override
     public void removeAllVisibleRecentTasks() {
         mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()");
@@ -2938,7 +2941,7 @@
                             }
                             getTransitionController().requestStartTransition(transition, task,
                                     null /* remoteTransition */, null /* displayChange */);
-                            getTransitionController().collect(task);
+                            transition.collect(task);
                             task.resize(bounds, resizeMode, preserveWindow);
                             transition.setReady(task, true);
                         });
@@ -3768,6 +3771,7 @@
             // Shell calls back into Core with the entry bounds to be applied with startWCT.
             final Transition enterPipTransition = new Transition(TRANSIT_PIP,
                     0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine);
+            r.setPictureInPictureParams(params);
             enterPipTransition.setPipActivity(r);
             r.mAutoEnteringPip = isAutoEnter;
             getTransitionController().startCollectOrQueue(enterPipTransition, (deferred) -> {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 8ef2693..1446c35 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1528,9 +1528,12 @@
             }
 
             mService.deferWindowLayout();
-            final Transition newTransition = task.mTransitionController.isShellTransitionsEnabled()
-                    ? task.mTransitionController.isCollecting() ? null
-                    : task.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+            boolean newTransition = false;
+            Transition transition = task.mTransitionController.getCollectingTransition();
+            if (transition == null && task.mTransitionController.isShellTransitionsEnabled()) {
+                transition = task.mTransitionController.createTransition(TRANSIT_TO_FRONT);
+                newTransition = true;
+            }
             task.mTransitionController.collect(task);
             reason = reason + " findTaskToMoveToFront";
             boolean reparented = false;
@@ -1574,8 +1577,8 @@
                 // transition to avoid delaying the starting window.
                 r.showStartingWindow(true /* taskSwitch */);
             }
-            if (newTransition != null) {
-                task.mTransitionController.requestStartTransition(newTransition, task,
+            if (newTransition) {
+                task.mTransitionController.requestStartTransition(transition, task,
                         options != null ? options.getRemoteTransition() : null,
                         null /* displayChange */);
             }
@@ -1644,7 +1647,7 @@
 
         mService.deferWindowLayout();
         try {
-            rootTask.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            rootTask.setRootTaskWindowingMode(WINDOWING_MODE_UNDEFINED);
             if (rootTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
                 rootTask.setBounds(null);
             }
@@ -1705,7 +1708,10 @@
             // Prevent recursion.
             return;
         }
-        final Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
+        Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
+        if (transit == null) {
+            transit = task.mTransitionController.getCollectingTransition();
+        }
         if (transit != null) {
             transit.collectClose(task);
             if (!task.mTransitionController.useFullReadyTracking()) {
@@ -1717,8 +1723,6 @@
                 // before anything that may need it to wait (setReady(false)).
                 transit.setReady(task, true);
             }
-        } else if (task.mTransitionController.isCollecting()) {
-            task.mTransitionController.getCollectingTransition().collectClose(task);
         }
         // Consume the stopping activities immediately so activity manager won't skip killing
         // the process because it is still foreground state, i.e. RESUMED -> PAUSING set from
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 1562cf6..a42b879 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -24,6 +24,7 @@
 import android.content.res.Configuration;
 import android.widget.Toast;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
 
 /**
@@ -32,7 +33,8 @@
 class AppCompatCameraPolicy {
 
     @Nullable
-    private final CameraStateMonitor mCameraStateMonitor;
+    @VisibleForTesting
+    final CameraStateMonitor mCameraStateMonitor;
     @Nullable
     private final ActivityRefresher mActivityRefresher;
     @Nullable
@@ -122,6 +124,9 @@
     }
 
     void start() {
+        if (mDisplayRotationCompatPolicy != null) {
+            mDisplayRotationCompatPolicy.start();
+        }
         if (mCameraCompatFreeformPolicy != null) {
             mCameraCompatFreeformPolicy.start();
         }
@@ -150,6 +155,10 @@
         return mCameraCompatFreeformPolicy != null;
     }
 
+    boolean hasCameraStateMonitor() {
+        return mCameraStateMonitor != null;
+    }
+
     @ScreenOrientation
     int getOrientation() {
         return mDisplayRotationCompatPolicy != null
diff --git a/services/core/java/com/android/server/wm/AppCompatConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
index ffa4251..42378aa 100644
--- a/services/core/java/com/android/server/wm/AppCompatConfiguration.java
+++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
@@ -30,6 +30,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.utils.DimenPxIntSupplier;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Function;
@@ -1382,6 +1383,26 @@
         setUserAppAspectRatioFullscreenOverrideEnabled(false);
     }
 
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        // TODO(b/359438445): Add more useful information to dump().
+        pw.println(prefix + "  letterboxPositionForHorizontalReachability="
+                + letterboxHorizontalReachabilityPositionToString(
+                    getLetterboxPositionForHorizontalReachability(
+                            /* isInFullScreenBookMode */ false)));
+        pw.println(prefix + "  letterboxPositionForVerticalReachability="
+                + letterboxVerticalReachabilityPositionToString(
+                    getLetterboxPositionForVerticalReachability(
+                            /* isInFullScreenTabletopMode */ false)));
+        pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
+                + getFixedOrientationLetterboxAspectRatio());
+        pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
+                + getDefaultMinAspectRatioForUnresizableApps());
+        pw.println(prefix + "  isSplitScreenAspectRatioForUnresizableAppsEnabled="
+                + getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+        pw.println(prefix + "  isDisplayAspectRatioEnabledForFixedOrientationLetterbox="
+                + getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
+    }
+
     /**
      * Checks whether the multiplier is between [0,1].
      *
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 4290051..906ee20 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -21,6 +21,8 @@
 
 import com.android.server.wm.utils.OptPropFactory;
 
+import java.io.PrintWriter;
+
 /**
  * Allows the interaction with all the app compat policies and configurations
  */
@@ -59,7 +61,8 @@
                 mTransparentPolicy, mAppCompatOverrides);
         mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord,
                 wmService.mAppCompatConfiguration);
-        mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord);
+        mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord,
+                wmService.mAppCompatConfiguration);
     }
 
     @NonNull
@@ -140,4 +143,9 @@
         return mAppCompatOverrides.getAppCompatLetterboxOverrides();
     }
 
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        getTransparentPolicy().dump(pw, prefix);
+        getAppCompatLetterboxPolicy().dump(pw, prefix);
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index d602c47..afc6506 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -32,6 +33,8 @@
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
 
+import java.io.PrintWriter;
+
 /**
  * Encapsulates the logic for the Letterboxing policy.
  */
@@ -43,15 +46,19 @@
     private final LetterboxPolicyState mLetterboxPolicyState;
     @NonNull
     private final AppCompatRoundedCorners mAppCompatRoundedCorners;
+    @NonNull
+    private final AppCompatConfiguration mAppCompatConfiguration;
 
     private boolean mLastShouldShowLetterboxUi;
 
-    AppCompatLetterboxPolicy(@NonNull ActivityRecord  activityRecord) {
+    AppCompatLetterboxPolicy(@NonNull ActivityRecord  activityRecord,
+            @NonNull AppCompatConfiguration appCompatConfiguration) {
         mActivityRecord = activityRecord;
         mLetterboxPolicyState = new LetterboxPolicyState();
         // TODO (b/358334569) Improve cutout logic dependency on app compat.
         mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord,
                 this::isLetterboxedNotForDisplayCutout);
+        mAppCompatConfiguration = appCompatConfiguration;
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -156,6 +163,38 @@
         return mAppCompatRoundedCorners.getRoundedCornersRadius(mainWindow);
     }
 
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final WindowState mainWin = mActivityRecord.findMainWindow();
+        if (mainWin == null) {
+            return;
+        }
+        boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed();
+        pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
+        pw.println(prefix + "isLetterboxRunning=" + isRunning());
+        if (!areBoundsLetterboxed) {
+            return;
+        }
+        pw.println(prefix + "  letterboxReason="
+                + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin));
+        mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy().dump(pw, prefix);
+        final AppCompatLetterboxOverrides letterboxOverride = mActivityRecord.mAppCompatController
+                .getAppCompatLetterboxOverrides();
+        pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
+                letterboxOverride.getLetterboxBackgroundColor().toArgb()));
+        pw.println(prefix + "  letterboxBackgroundType="
+                + letterboxBackgroundTypeToString(letterboxOverride.getLetterboxBackgroundType()));
+        pw.println(prefix + "  letterboxCornerRadius=" + getRoundedCornersRadius(mainWin));
+        if (letterboxOverride.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) {
+            pw.println(prefix + "  isLetterboxWallpaperBlurSupported="
+                    + letterboxOverride.isLetterboxWallpaperBlurSupported());
+            pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
+                    + letterboxOverride.getLetterboxWallpaperDarkScrimAlpha());
+            pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
+                    + letterboxOverride.getLetterboxWallpaperBlurRadiusPx());
+        }
+        mAppCompatConfiguration.dump(pw, prefix);
+    }
+
     private void updateWallpaperForLetterbox(@NonNull WindowState mainWindow) {
         final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
                 .mAppCompatController.getAppCompatLetterboxOverrides();
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index c3bf116..d03a803 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.util.function.Supplier;
 
 /**
@@ -74,6 +75,25 @@
         handleVerticalDoubleTap(y);
     }
 
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final AppCompatReachabilityOverrides reachabilityOverrides =
+                mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+        pw.println(prefix + "  isVerticalThinLetterboxed=" + reachabilityOverrides
+                .isVerticalThinLetterboxed());
+        pw.println(prefix + "  isHorizontalThinLetterboxed=" + reachabilityOverrides
+                .isHorizontalThinLetterboxed());
+        pw.println(prefix + "  isHorizontalReachabilityEnabled="
+                + reachabilityOverrides.isHorizontalReachabilityEnabled());
+        pw.println(prefix + "  isVerticalReachabilityEnabled="
+                + reachabilityOverrides.isVerticalReachabilityEnabled());
+        pw.println(prefix + "  letterboxHorizontalPositionMultiplier="
+                + reachabilityOverrides.getHorizontalPositionMultiplier(
+                mActivityRecord.getParent().getConfiguration()));
+        pw.println(prefix + "  letterboxVerticalPositionMultiplier="
+                + reachabilityOverrides.getVerticalPositionMultiplier(
+                mActivityRecord.getParent().getConfiguration()));
+    }
+
     private void handleHorizontalDoubleTap(int x) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
                 mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 89e10d36..767effd 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -367,7 +367,7 @@
                         // client process to prevent the unexpected relayout when handling the back
                         // animation.
                         for (int i = prevActivities.size() - 1; i >= 0; --i) {
-                            prevActivities.get(i).setDeferHidingClient(true);
+                            prevActivities.get(i).setDeferHidingClient();
                         }
                     } else {
                         scheduleAnimation(builder);
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 9b142f28..dda39a6 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -60,6 +60,11 @@
     @Nullable
     private Task mCameraTask;
 
+    /**
+     * Value toggled on {@link #start()} to {@code true} and on {@link #dispose()} to {@code false}.
+     */
+    private boolean mIsRunning;
+
     CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent,
             @NonNull CameraStateMonitor cameraStateMonitor,
             @NonNull ActivityRefresher activityRefresher) {
@@ -71,12 +76,19 @@
     void start() {
         mCameraStateMonitor.addCameraStateListener(this);
         mActivityRefresher.addEvaluator(this);
+        mIsRunning = true;
     }
 
     /** Releases camera callback listener. */
     void dispose() {
         mCameraStateMonitor.removeCameraStateListener(this);
         mActivityRefresher.removeEvaluator(this);
+        mIsRunning = false;
+    }
+
+    @VisibleForTesting
+    boolean isRunning() {
+        return mIsRunning;
     }
 
     // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 63c90ff..8bfef6d 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -26,6 +26,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 
 import java.util.ArrayList;
@@ -73,6 +74,12 @@
 
     private final ArrayList<CameraCompatStateListener> mCameraStateListeners = new ArrayList<>();
 
+    /**
+     * Value toggled on {@link #startListeningToCameraState()} to {@code true} and on {@link
+     * #dispose()} to {@code false}.
+     */
+    private boolean mIsRunning;
+
     private final CameraManager.AvailabilityCallback mAvailabilityCallback =
             new  CameraManager.AvailabilityCallback() {
                 @Override
@@ -101,6 +108,7 @@
     void startListeningToCameraState() {
         mCameraManager.registerAvailabilityCallback(
                 mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+        mIsRunning = true;
     }
 
     /** Releases camera callback listener. */
@@ -108,6 +116,12 @@
         if (mCameraManager != null) {
             mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
         }
+        mIsRunning = false;
+    }
+
+    @VisibleForTesting
+    boolean isRunning() {
+        return mIsRunning;
     }
 
     void addCameraStateListener(CameraCompatStateListener listener) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fcc6b11..c44e1b1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -45,7 +45,6 @@
 import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.Display.STATE_UNKNOWN;
 import static android.view.Display.isSuspendedState;
 import static android.view.InsetsSource.ID_IME;
@@ -80,6 +79,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -6556,7 +6556,12 @@
 
     @VisibleForTesting
     boolean shouldDestroyContentOnRemove() {
-        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+        return getRemoveContentMode() == REMOVE_CONTENT_MODE_DESTROY;
+    }
+
+    @WindowManager.RemoveContentMode
+    int getRemoveContentMode() {
+        return mWmService.mDisplayWindowSettings.getRemoveContentModeLocked(this);
     }
 
     boolean shouldSleep() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 762180b..27d9767 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -76,6 +76,11 @@
     @ScreenOrientation
     private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
 
+    /**
+     * Value toggled on {@link #start()} to {@code true} and on {@link #dispose()} to {@code false}.
+     */
+    private boolean mIsRunning;
+
     DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent,
             @NonNull CameraStateMonitor cameraStateMonitor,
             @NonNull ActivityRefresher activityRefresher) {
@@ -90,12 +95,19 @@
     void start() {
         mCameraStateMonitor.addCameraStateListener(this);
         mActivityRefresher.addEvaluator(this);
+        mIsRunning = true;
     }
 
     /** Releases camera state listener. */
     void dispose() {
         mCameraStateMonitor.removeCameraStateListener(this);
         mActivityRefresher.removeEvaluator(this);
+        mIsRunning = false;
+    }
+
+    @VisibleForTesting
+    boolean isRunning() {
+        return mIsRunning;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
deleted file mode 100644
index 0e8291e..0000000
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
-
-import android.annotation.NonNull;
-
-import java.io.PrintWriter;
-
-/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
-// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
-// SizeCompatTests and LetterboxTests but not all.
-final class LetterboxUiController {
-
-    private final AppCompatConfiguration mAppCompatConfiguration;
-
-    private final ActivityRecord mActivityRecord;
-
-    // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
-    @NonNull
-    private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
-    @NonNull
-    private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
-    @NonNull
-    private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
-
-    LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
-        mAppCompatConfiguration = wmService.mAppCompatConfiguration;
-        // Given activityRecord may not be fully constructed since LetterboxUiController
-        // is created in its constructor. It shouldn't be used in this constructor but it's safe
-        // to use it after since controller is only used in ActivityRecord.
-        mActivityRecord = activityRecord;
-        // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
-        mAppCompatReachabilityOverrides = mActivityRecord.mAppCompatController
-                .getAppCompatReachabilityOverrides();
-        mAppCompatLetterboxPolicy = mActivityRecord.mAppCompatController
-                .getAppCompatLetterboxPolicy();
-        mAppCompatLetterboxOverrides = mActivityRecord.mAppCompatController
-                .getAppCompatLetterboxOverrides();
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        final WindowState mainWin = mActivityRecord.findMainWindow();
-        if (mainWin == null) {
-            return;
-        }
-
-        pw.println(prefix + "isTransparentPolicyRunning="
-                + mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning());
-
-        boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed();
-        pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
-        if (!areBoundsLetterboxed) {
-            return;
-        }
-
-        pw.println(prefix + "  letterboxReason="
-                + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin));
-        pw.println(prefix + "  activityAspectRatio="
-                + AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds()));
-
-        boolean shouldShowLetterboxUi = mAppCompatLetterboxPolicy.shouldShowLetterboxUi(mainWin);
-        pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi);
-
-        if (!shouldShowLetterboxUi) {
-            return;
-        }
-        pw.println(prefix + "  isVerticalThinLetterboxed="
-                + mAppCompatReachabilityOverrides.isVerticalThinLetterboxed());
-        pw.println(prefix + "  isHorizontalThinLetterboxed="
-                + mAppCompatReachabilityOverrides.isHorizontalThinLetterboxed());
-        pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
-                mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().toArgb()));
-        pw.println(prefix + "  letterboxBackgroundType="
-                + letterboxBackgroundTypeToString(
-                        mAppCompatConfiguration.getLetterboxBackgroundType()));
-        pw.println(prefix + "  letterboxCornerRadius="
-                + mAppCompatLetterboxPolicy.getRoundedCornersRadius(mainWin));
-        if (mAppCompatConfiguration.getLetterboxBackgroundType()
-                == LETTERBOX_BACKGROUND_WALLPAPER) {
-            pw.println(prefix + "  isLetterboxWallpaperBlurSupported="
-                    + mAppCompatLetterboxOverrides.isLetterboxWallpaperBlurSupported());
-            pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
-                    + mAppCompatLetterboxOverrides.getLetterboxWallpaperDarkScrimAlpha());
-            pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
-                    + mAppCompatLetterboxOverrides.getLetterboxWallpaperBlurRadiusPx());
-        }
-        final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord
-                .mAppCompatController.getAppCompatReachabilityOverrides();
-        pw.println(prefix + "  isHorizontalReachabilityEnabled="
-                + reachabilityOverrides.isHorizontalReachabilityEnabled());
-        pw.println(prefix + "  isVerticalReachabilityEnabled="
-                + reachabilityOverrides.isVerticalReachabilityEnabled());
-        pw.println(prefix + "  letterboxHorizontalPositionMultiplier="
-                + mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(mActivityRecord
-                    .getParent().getConfiguration()));
-        pw.println(prefix + "  letterboxVerticalPositionMultiplier="
-                + mAppCompatReachabilityOverrides.getVerticalPositionMultiplier(mActivityRecord
-                    .getParent().getConfiguration()));
-        pw.println(prefix + "  letterboxPositionForHorizontalReachability="
-                + AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString(
-                mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(false)));
-        pw.println(prefix + "  letterboxPositionForVerticalReachability="
-                + AppCompatConfiguration.letterboxVerticalReachabilityPositionToString(
-                mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(false)));
-        pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
-                + mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio());
-        pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
-                + mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps());
-        pw.println(prefix + "  isSplitScreenAspectRatioForUnresizableAppsEnabled="
-                + mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-        pw.println(prefix + "  isDisplayAspectRatioEnabledForFixedOrientationLetterbox="
-                + mAppCompatConfiguration
-                .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
-    }
-}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bded98c..6427c32 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1633,7 +1633,7 @@
         // Only resume home activity if isn't finishing.
         if (r != null && !r.finishing) {
             r.moveFocusableActivityToTop(myReason);
-            return resumeFocusedTasksTopActivities(r.getRootTask(), prev, null);
+            return resumeFocusedTasksTopActivities(r.getRootTask(), prev);
         }
         int userId = mWmService.getUserAssignedToDisplay(taskDisplayArea.getDisplayId());
         return startHomeOnTaskDisplayArea(userId, myReason, taskDisplayArea,
@@ -2202,7 +2202,7 @@
                     // During recents animations, the original task is "occluded" by launcher but
                     // it wasn't paused (due to transient-launch). If we reparent to the (top) task
                     // now, it will take focus briefly which confuses the RecentTasks tracker.
-                    rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
+                    rootTask.setRootTaskWindowingMode(WINDOWING_MODE_PINNED);
                 }
                 // Temporarily disable focus when reparenting to avoid intermediate focus change
                 // (because the task is on top and the activity is resumed), which could cause the
@@ -2235,7 +2235,7 @@
 
             // TODO(remove-legacy-transit): Move this to the `singleActivity` case when removing
             //                              legacy transit.
-            rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
+            rootTask.setRootTaskWindowingMode(WINDOWING_MODE_PINNED);
             if (isPip2ExperimentEnabled() && bounds != null) {
                 // set the final pip bounds in advance if pip2 is enabled
                 rootTask.setBounds(bounds);
@@ -2470,12 +2470,12 @@
     }
 
     boolean resumeFocusedTasksTopActivities() {
-        return resumeFocusedTasksTopActivities(null, null, null);
+        return resumeFocusedTasksTopActivities(null, null, null, false /* deferPause */);
     }
 
     boolean resumeFocusedTasksTopActivities(
-            Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions) {
-        return resumeFocusedTasksTopActivities(targetRootTask, target, targetOptions,
+            Task targetRootTask, ActivityRecord target) {
+        return resumeFocusedTasksTopActivities(targetRootTask, target, null /* targetOptions */,
                 false /* deferPause */);
     }
 
@@ -2527,7 +2527,8 @@
                 // activity is started and resumed, and no recursion occurs.
                 final Task focusedRoot = display.getFocusedRootTask();
                 if (focusedRoot != null) {
-                    result |= focusedRoot.resumeTopActivityUncheckedLocked(target, targetOptions);
+                    result |= focusedRoot.resumeTopActivityUncheckedLocked(
+                            target, targetOptions, false /* skipPause */);
                 } else if (targetRootTask == null) {
                     result |= resumeHomeActivity(null /* prev */, "no-focusable-task",
                             display.getDefaultTaskDisplayArea());
@@ -2632,7 +2633,7 @@
                         // process the keyguard going away, which can happen before the sleep
                         // token is released. As a result, it is important we resume the
                         // activity here.
-                        rootTask.resumeTopActivityUncheckedLocked(null, null);
+                        rootTask.resumeTopActivityUncheckedLocked();
                     }
                     // The visibility update must not be called before resuming the top, so the
                     // display orientation can be updated first if needed. Otherwise there may
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 12e91ad..4340771 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4540,7 +4540,8 @@
             reparent(parent, MAX_VALUE);
         }
 
-        setWindowingMode(mMultiWindowRestoreWindowingMode);
+        // mMultiWindowRestoreWindowingMode is INVALID for non-root tasks
+        setRootTaskWindowingMode(mMultiWindowRestoreWindowingMode);
     }
 
     @Override
@@ -4553,18 +4554,30 @@
             return;
         }
 
-        setWindowingMode(windowingMode, false /* creating */);
+        setWindowingModeInner(windowingMode, false /* creating */);
     }
 
     /**
-     * Specialization of {@link #setWindowingMode(int)} for this subclass.
+     * Version of {@link #setWindowingMode(int)} for root tasks.
      *
      * @param preferredWindowingMode the preferred windowing mode. This may not be honored depending
      *         on the state of things. For example, WINDOWING_MODE_UNDEFINED will resolve to the
      *         previous non-transient mode if this root task is currently in a transient mode.
+     */
+    public void setRootTaskWindowingMode(int preferredWindowingMode) {
+        if (!isRootTask()) {
+            Slog.wtf(TAG, "Trying to set root-task windowing-mode on a non-root-task: " + this,
+                    new Throwable());
+            super.setWindowingMode(preferredWindowingMode);
+            return;
+        }
+        setWindowingModeInner(preferredWindowingMode, false /* creating */);
+    }
+
+    /**
      * @param creating {@code true} if this is being run during task construction.
      */
-    void setWindowingMode(int preferredWindowingMode, boolean creating) {
+    private void setWindowingModeInner(int preferredWindowingMode, boolean creating) {
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
         if (taskDisplayArea == null) {
             Slog.d(TAG, "taskDisplayArea is null, bail early");
@@ -4657,6 +4670,9 @@
                     mTransitionController.collect(topActivity);
 
                     final Task lastParentBeforePip = topActivity.getLastParentBeforePip();
+                    // Reset the activity windowing mode to match the parent.
+                    topActivity.getRequestedOverrideConfiguration()
+                            .windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
                     topActivity.reparent(lastParentBeforePip,
                             lastParentBeforePip.getChildCount() /* top */,
                             "movePinnedActivityToOriginalTask");
@@ -4689,6 +4705,15 @@
                 // it does not follow the ActivityStarter path.
                 if (topActivity.shouldBeVisible()) {
                     mAtmService.resumeAppSwitches();
+                    // In pip1, when expanding pip to full-screen, the "behind" task is not
+                    // actually becoming invisible since task windowing mode is pinned.
+                    if (!isPip2ExperimentEnabled) {
+                        final ActivityRecord ar = mAtmService.mLastResumedActivity;
+                        if (ar != null && ar.getTask() != null) {
+                            mAtmService.takeTaskSnapshot(ar.getTask().mTaskId,
+                                    true /* updateCache */);
+                        }
+                    }
                 }
             } else if (isPip2ExperimentEnabled) {
                 super.setWindowingMode(windowingMode);
@@ -4740,7 +4765,7 @@
             }
         }
         if (isAttached()) {
-            setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            setRootTaskWindowingMode(WINDOWING_MODE_UNDEFINED);
             moveTaskToBackInner(this, null /* transition */);
         }
         if (top.isAttached()) {
@@ -5086,10 +5111,10 @@
         return someActivityResumed;
     }
 
-    /** @see #resumeTopActivityUncheckedLocked(ActivityRecord, ActivityOptions, boolean) */
     @GuardedBy("mService")
-    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
-        return resumeTopActivityUncheckedLocked(prev, options, false /* skipPause */);
+    boolean resumeTopActivityUncheckedLocked() {
+        return resumeTopActivityUncheckedLocked(null /* prev */, null /* options */,
+                false /* skipPause */);
     }
 
     @GuardedBy("mService")
@@ -5138,8 +5163,7 @@
                 // Try to move focus to the next visible root task with a running activity if this
                 // root task is not covering the entire screen or is on a secondary display with
                 // no home root task.
-                return mRootWindowContainer.resumeFocusedTasksTopActivities(nextFocusedTask,
-                        prev, null /* targetOptions */);
+                return mRootWindowContainer.resumeFocusedTasksTopActivities(nextFocusedTask, prev);
             }
         }
 
@@ -5661,8 +5685,10 @@
             if (noAnimation) {
                 mDisplayContent.prepareAppTransition(TRANSIT_NONE);
                 mTaskSupervisor.mNoAnimActivities.add(top);
-                mTransitionController.collect(top);
-                mTransitionController.setNoAnimation(top);
+                if (mTransitionController.isShellTransitionsEnabled()) {
+                    mTransitionController.collect(top);
+                    mTransitionController.setNoAnimation(top);
+                }
                 ActivityOptions.abort(options);
             } else {
                 updateTransitLocked(TRANSIT_TO_FRONT, options);
@@ -6002,7 +6028,7 @@
         }
 
         final Task task = getBottomMostTask();
-        setWindowingMode(WINDOWING_MODE_UNDEFINED);
+        setRootTaskWindowingMode(WINDOWING_MODE_UNDEFINED);
 
         // Task could have been removed from the hierarchy due to windowing mode change
         // where its only child is reparented back to their original parent task.
@@ -6674,7 +6700,7 @@
 
             // Set windowing mode after attached to display area or it abort silently.
             if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
-                task.setWindowingMode(mWindowingMode, true /* creating */);
+                task.setWindowingModeInner(mWindowingMode, true /* creating */);
             }
             return task;
         }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index dba1c36..d9e88e1 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -923,7 +923,7 @@
             if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
                     && candidateTask.getWindowingMode() != windowingMode) {
                 candidateTask.mTransitionController.collect(candidateTask);
-                candidateTask.setWindowingMode(windowingMode);
+                candidateTask.setRootTaskWindowingMode(windowingMode);
             }
             return candidateTask.getRootTask();
         }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2ee157f..329d11b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1927,7 +1927,7 @@
                     prev.setState(STOPPING, "completePausedLocked");
                 } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
                     // Clear out any deferred client hide we might currently have.
-                    prev.setDeferHidingClient(false);
+                    prev.clearDeferHidingClient();
                     // If we were visible then resumeTopActivities will release resources before
                     // stopping.
                     prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
@@ -1948,8 +1948,7 @@
         if (resumeNext) {
             final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
             if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
-                mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev,
-                        null /* targetOptions */);
+                mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev);
             } else {
                 // checkReadyForSleep();
                 final ActivityRecord top =
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9ee1fbe..de2e4f5 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -390,7 +390,7 @@
                 t.mTaskAppearedSent = false;
             }
             if (removeFromSystem) {
-                mService.removeTask(t.mTaskId);
+                mService.removeTask(t);
             }
             return taskAppearedSent;
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 836ac5f..b768bb1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -420,6 +420,18 @@
                 updateTransientFlags(mChanges.valueAt(i));
             }
         }
+
+        // TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+        // Also interpret HOME transient launch as recents
+        if (activity.isActivityTypeHomeOrRecents()) {
+            addFlag(TRANSIT_FLAG_IS_RECENTS);
+            // When starting recents animation, we assume the recents activity is behind the app
+            // task and should not affect system bar appearance,
+            // until WMS#setRecentsAppBehindSystemBars be called from launcher when passing
+            // the gesture threshold.
+            activity.getTask().setCanAffectSystemUiFlags(false);
+        }
+
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
                 + "transient-launch", mSyncId, activity);
     }
@@ -1721,6 +1733,7 @@
                 transaction.addTransactionCompletedListener(Runnable::run,
                         (stats) -> listener.run());
             }
+            mTransactionCompletedListeners = null;
         }
 
         // Fall-back to the default display if there isn't one participating.
@@ -1763,6 +1776,7 @@
         if (mPriorVisibilityMightBeDirty) {
             updatePriorVisibility();
         }
+
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
 
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 67d7b37..ef25eda 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -19,7 +19,6 @@
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_NONE;
 
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
@@ -53,8 +52,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.server.FgThread;
 import com.android.window.flags.Flags;
 
@@ -1311,23 +1310,6 @@
     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelowTask) {
         if (mCollectingTransition == null) return;
         mCollectingTransition.setTransientLaunch(activity, restoreBelowTask);
-
-        // TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
-        // Also interpret HOME transient launch as recents
-        if (activity.isActivityTypeHomeOrRecents()) {
-            mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
-            // When starting recents animation, we assume the recents activity is behind the app
-            // task and should not affect system bar appearance,
-            // until WMS#setRecentsAppBehindSystemBars be called from launcher when passing
-            // the gesture threshold.
-            activity.getTask().setCanAffectSystemUiFlags(false);
-        }
-    }
-
-    /** @see Transition#setCanPipOnFinish */
-    void setCanPipOnFinish(boolean canPipOnFinish) {
-        if (mCollectingTransition == null) return;
-        mCollectingTransition.setCanPipOnFinish(canPipOnFinish);
     }
 
     void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
@@ -1459,7 +1441,7 @@
      *
      * WARNING: ONLY use this if the transition absolutely cannot be deferred!
      */
-    @NonNull
+    @Nullable
     Transition createAndStartCollecting(int type) {
         if (mTransitionPlayers.isEmpty()) {
             return null;
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 39b2635..36bc846 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -31,6 +31,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -188,6 +189,10 @@
         return mTransparentPolicyState.findOpaqueNotFinishingActivityBelow();
     }
 
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.println(prefix + "isTransparentPolicyRunning=" + isRunning());
+    }
+
     // We evaluate the case when the policy should not be applied.
     private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) {
         if (opaqueActivity == null || opaqueActivity.isEmbedded()) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 64f9c01..15d67eb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1611,8 +1611,8 @@
      *
      * @param orientation the specified orientation.
      */
-    void setOrientation(@ScreenOrientation int orientation) {
-        setOrientation(orientation, null /* requestingContainer */);
+    protected void setOrientation(@ScreenOrientation int orientation) {
+        setOrientation(orientation, null /* requesting */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5749272..243ab3a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7656,7 +7656,7 @@
                         displayId);
                 return REMOVE_CONTENT_MODE_UNDEFINED;
             }
-            return mDisplayWindowSettings.getRemoveContentModeLocked(displayContent);
+            return displayContent.getRemoveContentMode();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c6d0b72..9d40b16 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -322,11 +322,11 @@
                     // This is a direct call from shell, so the entire transition lifecycle is
                     // contained in the provided transaction if provided. Thus, we can setReady
                     // immediately after apply.
-                    final Transition.ReadyCondition wctApplied =
-                            new Transition.ReadyCondition("start WCT applied");
                     final boolean needsSetReady = t != null;
                     final Transition nextTransition = new Transition(type, 0 /* flags */,
                             mTransitionController, mService.mWindowManager.mSyncEngine);
+                    final Transition.ReadyCondition wctApplied =
+                            new Transition.ReadyCondition("start WCT applied");
                     nextTransition.mReadyTracker.add(wctApplied);
                     nextTransition.calcParallelCollectType(wct);
                     mTransitionController.startCollectOrQueue(nextTransition,
@@ -342,6 +342,15 @@
                             });
                     return nextTransition.getToken();
                 }
+                // The transition already started collecting before sending a request to shell,
+                // so just start here.
+                if (!transition.isCollecting() && !transition.isForcePlaying()) {
+                    Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
+                            + " means Shell took too long to respond to a request. WM State may be"
+                            + " incorrect now, please file a bug");
+                    applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+                    return transition.getToken();
+                }
                 // Currently, application of wct can span multiple looper loops (ie.
                 // waitAsyncStart), so add a condition to ensure that it finishes applying.
                 final Transition.ReadyCondition wctApplied;
@@ -351,18 +360,6 @@
                 } else {
                     wctApplied = null;
                 }
-                // The transition already started collecting before sending a request to shell,
-                // so just start here.
-                if (!transition.isCollecting() && !transition.isForcePlaying()) {
-                    Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
-                            + " means Shell took too long to respond to a request. WM State may be"
-                            + " incorrect now, please file a bug");
-                    applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
-                    if (wctApplied != null) {
-                        wctApplied.meet();
-                    }
-                    return transition.getToken();
-                }
                 transition.mLogger.mStartWCT = wct;
                 if (transition.shouldApplyOnDisplayThread()) {
                     mService.mH.post(() -> {
@@ -851,7 +848,11 @@
             }
 
             final int prevMode = container.getRequestedOverrideWindowingMode();
-            container.setWindowingMode(windowingMode);
+            if (container.asTask() != null && container.asTask().isRootTask()) {
+                container.asTask().setRootTaskWindowingMode(windowingMode);
+            } else {
+                container.setWindowingMode(windowingMode);
+            }
             if (prevMode != container.getWindowingMode()) {
                 // The activity in the container may become focusable or non-focusable due to
                 // windowing modes changes (such as entering or leaving pinned windowing mode),
diff --git a/services/proguard.flags b/services/proguard.flags
index bf30781..35e27f8 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -52,9 +52,6 @@
 -keep public class com.android.server.utils.Slogf { *; }
 
 # Referenced in wear-service
-# HIDL interfaces
--keep public class android.hidl.base.** { *; }
--keep public class android.hidl.manager.** { *; }
 -keep public class com.android.server.wm.WindowManagerInternal { *; }
 
 # JNI keep rules
@@ -107,8 +104,16 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.SystemService { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.SystemService$TargetUser { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.usage.StorageStatsManagerLocal { *; }
--keep,allowoptimization,allowaccessmodification class com.android.internal.util.** { *; }
--keep,allowoptimization,allowaccessmodification class android.os.** { *; }
+
+# Prevent optimizations of any statically linked code that may shadow code in
+# the bootclasspath. See also StrictJavaPackagesTest for details on exceptions.
+# TODO(b/222468116): Resolve such collisions in the build system.
+-keep public class android.gsi.** { *; }
+-keep public class android.hidl.base.** { *; }
+-keep public class android.hidl.manager.** { *; }
+-keep public class android.os.** { *; }
+-keep public class com.android.internal.util.** { *; }
+-keep public class com.android.modules.utils.build.** { *; }
 
 # CoverageService guards optional jacoco class references with a runtime guard, so we can safely
 # suppress build-time warnings.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 2a458c42..312df43 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -194,7 +194,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */,
                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
     }
 
     @Test
@@ -300,7 +300,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         // There should be a user data point added to the mapper.
         verify(mBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f,
@@ -324,7 +324,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         //Recalculating the spline with RBC enabled, verifying that the short term model is reset,
         //and the interaction is learnt in short term model
@@ -358,7 +358,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                 /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
 
@@ -398,7 +398,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                 anyFloat(), anyFloat())).thenReturn(true);
@@ -438,7 +438,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                 /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
@@ -484,7 +484,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                 /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
 
@@ -571,7 +571,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         // There should be a user data point added to the mapper.
         verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f,
@@ -598,7 +598,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         // Ensure we use the correct mapping strategy
         verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f,
@@ -759,7 +759,8 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ true);
+                Display.STATE_ON, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ true);
         assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
         // The raw brightness value should not have throttling applied
         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
@@ -770,7 +771,8 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ true);
+                Display.STATE_ON, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ true);
         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
     }
@@ -907,13 +909,15 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ false);
+                Display.STATE_ON, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false);
         verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
 
         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ true);
+                Display.STATE_ON, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ true);
         verify(mBrightnessMappingStrategy).clearUserDataPoints();
     }
 
@@ -1100,7 +1104,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                 /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_DOZE,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
 
         // Send a new sensor value
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
@@ -1112,6 +1116,77 @@
     }
 
     @Test
+    public void testAutoBrightnessInDoze_useNormalBrightnessForDozeFalse_scaleScreenOn()
+            throws Exception {
+        when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.3f as a brightness value
+        float lux = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness = 0.3f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+        // Set policy to DOZE
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_ON,
+                /* useNormalBrightnessForDoze= */ false, /* shouldResetShortTermModel= */ true);
+
+        // Send a new sensor value
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+        // The brightness should be scaled by the doze factor
+        assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+                mController.getAutomaticScreenBrightness(
+                        /* brightnessEvent= */ null), EPSILON);
+    }
+
+    @Test
+    public void testAutoBrightnessInDoze_useNormalBrightnessForDozeTrue_notScaleScreenOn()
+            throws Exception {
+        when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.3f as a brightness value
+        float lux = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness = 0.3f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+        // Set policy to DOZE
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_ON,
+                /* useNormalBrightnessForDoze= */ true, /* shouldResetShortTermModel= */ true);
+
+        // Send a new sensor value
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+        // The brightness should not be scaled by the doze factor
+        assertEquals(normalizedBrightness,
+                mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
+    }
+
+    @Test
     public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
         ArgumentCaptor<SensorEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
@@ -1136,39 +1211,7 @@
         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                 /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_DOZE,
-                /* shouldResetShortTermModel= */ true);
-
-        // Send a new sensor value
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
-
-        // The brightness should not be scaled by the doze factor
-        assertEquals(normalizedBrightness,
-                mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
-    }
-
-    @Test
-    public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Set up system to return 0.3f as a brightness value
-        float lux = 100.0f;
-        // Brightness as float (from 0.0f to 1.0f)
-        float normalizedBrightness = 0.3f;
-        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
-        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
-        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
-                /* category= */ anyInt())).thenReturn(normalizedBrightness);
-        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-
-        // Set policy to DOZE
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
-                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_ON,
-                /* shouldResetShortTermModel= */ true);
+                /* useNormalBrightnessForDoze= */ true, /* shouldResetShortTermModel= */ true);
 
         // Send a new sensor value
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index b5278a5..0bcc572 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1014,7 +1014,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ false
+                Display.STATE_ON, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController)
                 .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
@@ -1040,7 +1041,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
-                Display.STATE_DOZE, /* shouldResetShortTermModel= */ false
+                Display.STATE_DOZE, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController)
                 .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
@@ -1070,7 +1072,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
-                Display.STATE_DOZE, /* shouldResetShortTermModel= */ false
+                Display.STATE_DOZE, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController)
                 .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
@@ -1093,7 +1096,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ false
+                Display.STATE_ON, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController)
                 .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
@@ -1116,7 +1120,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
-                Display.STATE_OFF, /* shouldResetShortTermModel= */ false
+                Display.STATE_OFF, /* useNormalBrightnessForDoze */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController).setAutoBrightnessEnabled(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
@@ -1142,7 +1147,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
-                Display.STATE_DOZE, /* shouldResetShortTermModel= */ false
+                Display.STATE_DOZE, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController).setAutoBrightnessEnabled(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
@@ -1172,7 +1178,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
-                Display.STATE_DOZE, /* shouldResetShortTermModel= */ false
+                Display.STATE_DOZE, /* useNormalBrightnessForDoze= */ false,
+                /* shouldResetShortTermModel= */ false
         );
         verify(mHolder.hbmController).setAutoBrightnessEnabled(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
@@ -1196,7 +1203,8 @@
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, /* shouldResetShortTermModel= */ false
+                Display.STATE_ON, /* useNormalBrightnessForDoze */ false,
+                /* shouldResetShortTermModel= */ false
         );
 
         // HBM should be allowed for the follower display
@@ -1955,6 +1963,7 @@
                 /* userChangedAutoBrightnessAdjustment= */ anyBoolean(),
                 /* displayPolicy= */ anyInt(),
                 /* displayState= */ anyInt(),
+                /* useNormalBrightnessForDoze= */ anyBoolean(),
                 /* shouldResetShortTermModel= */ anyBoolean());
         verify(mBrightnessTrackerMock, never()).notifyBrightnessChanged(
                 /* brightness= */ anyFloat(),
@@ -2040,6 +2049,75 @@
     }
 
     @Test
+    public void testManualBrightness_stateOnPolicyDozeUseNormalBrightnessForDozeFalse_brightnessDoze() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        float brightness = 0.277f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        when(mHolder.hbmController.getCurrentBrightnessMax())
+                .thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.dozeScreenState = Display.STATE_ON;
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        dpr.useNormalBrightnessForDoze = false;
+        // Confirm using doze brightness for dozing device.
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState, initialize
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
+                /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+                /* ignoreAnimationLimits= */ anyBoolean());
+    }
+
+    @Test
+    public void testManualBrightness_stateOnPolicyDozeUseNormalBrightnessForDozeTrue_brightnessNormal() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        float brightness = 0.277f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        when(mHolder.hbmController.getCurrentBrightnessMax())
+                .thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.dozeScreenState = Display.STATE_ON;
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        dpr.useNormalBrightnessForDoze = true;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState, initialize
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness),
+                /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+                /* ignoreAnimationLimits= */ anyBoolean());
+    }
+
+    @Test
     public void testDozeManualBrightness_AbcIsNull() {
         when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true,
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 639d06d..a44c517 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
@@ -187,7 +187,7 @@
     }
 
     @Test
-    public void selectStrategySelectsDozeStrategyWhenValid() {
+    public void selectStrategyWhenValid_useNormalBrightnessForDozeFalse_SelectsDozeStrategy() {
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
                 DisplayManagerInternal.DisplayPowerRequest.class);
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
@@ -201,6 +201,22 @@
     }
 
     @Test
+    public void selectStrategyWhenValid_useNormalBrightnessForDozeTrue_doNotSelectsDozeStrategy() {
+        when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.dozeScreenBrightness = 0.2f;
+        displayPowerRequest.useNormalBrightnessForDoze = true;
+        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
+        assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
+                                0.1f, false, mDisplayOffloadSession)),
+                mDozeBrightnessModeStrategy);
+    }
+
+    @Test
     public void selectStrategyDoesNotSelectDozeStrategyWhenInvalidBrightness() {
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
                 DisplayManagerInternal.DisplayPowerRequest.class);
@@ -353,7 +369,8 @@
                 mAutomaticBrightnessStrategy);
         verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON,
                 true, BrightnessReason.REASON_UNKNOWN,
-                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f, false);
+                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
+                /* useNormalBrightnessForDoze= */ false, 0.1f, false);
     }
 
     @Test
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
index 4e10b98..e386542 100644
--- 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
@@ -56,6 +56,9 @@
 @RunWith(AndroidJUnit4.class)
 public class AutomaticBrightnessStrategy2Test {
     private static final int DISPLAY_ID = 0;
+
+    private static final boolean DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE = false;
+
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
@@ -78,7 +81,8 @@
                 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);
+        mAutomaticBrightnessStrategy =
+                new AutomaticBrightnessStrategy2(mContext, DISPLAY_ID);
 
         mBrightnessConfiguration = new BrightnessConfiguration.Builder(
                 new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
@@ -106,15 +110,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.5f,
                         /* userChangedAutoBrightnessAdjustment= */ false, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -130,15 +137,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                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);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -152,17 +162,20 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
+        boolean useNormalBrightnessForDoze = false;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze,
+                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);
+                        targetDisplayState,
+                        useNormalBrightnessForDoze, /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -178,15 +191,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.5f,
                         /* userChangedAutoBrightnessAdjustment= */ false, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -200,19 +216,21 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
+        boolean useNormalBrightnessForDoze = false;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.4f,
-                        /* userChangedAutoBrightnessAdjustment= */ true, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        useNormalBrightnessForDoze, /* shouldResetShortTermModel */ true);
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -226,19 +244,20 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
+        boolean useNormalBrightnessForDoze = false;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.4f,
-                        /* userChangedAutoBrightnessAdjustment= */ true, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        useNormalBrightnessForDoze, /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -257,14 +276,16 @@
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, pendingBrightnessAdjustment,
                         /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
                         /* shouldResetShortTermModel */ true);
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
@@ -280,19 +301,20 @@
         boolean userSetBrightnessChanged = true;
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float pendingBrightnessAdjustment = 0.1f;
+        boolean useNormalBrightnessForDoze = false;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, pendingBrightnessAdjustment,
                         /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
-                        /* shouldResetShortTermModel */ true);
+                        useNormalBrightnessForDoze, /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -312,13 +334,15 @@
         mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
         setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
         mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
-                autoBrightnessState);
+                lastUserSetScreenBrightness, policy, targetDisplayState,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                brightnessConfiguration, autoBrightnessState);
         verify(mAutomaticBrightnessController).configure(autoBrightnessState,
                 brightnessConfiguration,
                 lastUserSetScreenBrightness,
                 userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
                 /* userChangedAutoBrightnessAdjustment= */ false, policy, targetDisplayState,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
                 /* shouldResetShortTermModel= */ true);
         assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
         assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
@@ -328,8 +352,9 @@
         mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
         mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
         mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
-                autoBrightnessState);
+                lastUserSetScreenBrightness, policy, targetDisplayState,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                brightnessConfiguration, autoBrightnessState);
         assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
         assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
         assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
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 93ff9e1..50f814d 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
@@ -23,7 +23,9 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -62,6 +64,9 @@
 @RunWith(AndroidJUnit4.class)
 public class AutomaticBrightnessStrategyTest {
     private static final int DISPLAY_ID = 0;
+
+    private static final boolean DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE = false;
+
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
@@ -116,15 +121,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.5f,
                         /* userChangedAutoBrightnessAdjustment= */ false, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -140,15 +148,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                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);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -164,15 +175,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                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);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -188,15 +202,18 @@
         boolean userSetBrightnessChanged = true;
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.5f,
                         /* userChangedAutoBrightnessAdjustment= */ false, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -214,15 +231,17 @@
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.4f,
-                        /* userChangedAutoBrightnessAdjustment= */ true, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -240,15 +259,17 @@
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, lastUserSetBrightness,
                 userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, /* adjustment */ 0.4f,
-                        /* userChangedAutoBrightnessAdjustment= */ true, policy,
-                        targetDisplayState, /* shouldResetShortTermModel */ true);
+                        /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                        /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
     }
@@ -267,14 +288,16 @@
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
                         mBrightnessConfiguration,
                         lastUserSetBrightness,
                         userSetBrightnessChanged, pendingBrightnessAdjustment,
                         /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
                         /* shouldResetShortTermModel */ true);
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
@@ -294,7 +317,8 @@
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, lastUserSetBrightness,
                 userSetBrightnessChanged);
         verify(mAutomaticBrightnessController)
                 .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
@@ -302,6 +326,7 @@
                         lastUserSetBrightness,
                         userSetBrightnessChanged, pendingBrightnessAdjustment,
                         /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
                         /* shouldResetShortTermModel */ true);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
@@ -318,6 +343,7 @@
         boolean userSetBrightnessChanged = true;
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float pendingBrightnessAdjustment = 0.1f;
+        boolean useNormalBrightnessForDoze = false;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
         mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
@@ -325,8 +351,8 @@
         // Validate no interaction when automaticBrightnessController is in idle mode
         when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(true);
         mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController, never())
                 .switchMode(anyInt(), /* sendUpdate= */ anyBoolean());
 
@@ -334,20 +360,57 @@
         // state is ON
         when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false);
         mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
         verify(mAutomaticBrightnessController).switchMode(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
                 /* sendUpdate= */ false);
 
-        // Validate interaction when automaticBrightnessController is in non-idle mode, and display
-        // state is DOZE
+        reset(mAutomaticBrightnessController);
+        when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false);
+        when(mDisplayManagerFlags.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true);
+        policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
+        // Validate interaction when automaticBrightnessController is in non-idle mode, display
+        // state is DOZE, policy is DOZE and useNormalBrightnessForDoze is false.
         mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+        // 1st AUTO_BRIGHTNESS_MODE_DOZE
         verify(mAutomaticBrightnessController).switchMode(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE,
                 /* sendUpdate= */ false);
+
+        // Validate interaction when automaticBrightnessController is in non-idle mode, display
+        // state is ON, policy is DOZE and useNormalBrightnessForDoze is false.
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+        // 2nd AUTO_BRIGHTNESS_MODE_DOZE
+        verify(mAutomaticBrightnessController, times(2)).switchMode(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE,
+                /* sendUpdate= */ false);
+
+        useNormalBrightnessForDoze = true;
+        // Validate interaction when automaticBrightnessController is in non-idle mode, display
+        // state is DOZE, policy is DOZE and useNormalBrightnessForDoze is true.
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+        // 1st AUTO_BRIGHTNESS_MODE_DEFAULT
+        verify(mAutomaticBrightnessController).switchMode(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* sendUpdate= */ false);
+
+        // Validate interaction when automaticBrightnessController is in non-idle mode, display
+        // state is ON, policy is DOZE and useNormalBrightnessForDoze is true.
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy,
+                useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged);
+        // 2nd AUTO_BRIGHTNESS_MODE_DEFAULT
+        verify(mAutomaticBrightnessController, times(2)).switchMode(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* sendUpdate= */ false);
     }
 
     @Test
@@ -365,13 +428,15 @@
         mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
         setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
         mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
-                autoBrightnessState);
+                lastUserSetScreenBrightness, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                brightnessConfiguration, autoBrightnessState);
         verify(mAutomaticBrightnessController).configure(autoBrightnessState,
                 brightnessConfiguration,
                 lastUserSetScreenBrightness,
                 userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
                 /* userChangedAutoBrightnessAdjustment= */ false, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
                 /* shouldResetShortTermModel= */ true);
         assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
         assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
@@ -381,8 +446,9 @@
         mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
         mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
         mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
-                autoBrightnessState);
+                lastUserSetScreenBrightness, policy, targetDisplayState,
+                        DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                brightnessConfiguration, autoBrightnessState);
         assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
         assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
         assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
@@ -504,8 +570,9 @@
     public void isAutoBrightnessValid_returnsFalseWhenBrightnessIsInvalid() {
         mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true,
                 BrightnessReason.REASON_UNKNOWN,
-                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f,
-                false);
+                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, /* lastUserSetScreenBrightness= */ 0.1f,
+                /* userSetBrightnessChanged= */ false);
         when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
                 .thenReturn(Float.NaN);
         assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
@@ -520,8 +587,9 @@
                 .thenReturn(0.1f);
         mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true,
                 BrightnessReason.REASON_UNKNOWN,
-                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f,
-                false);
+                DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
+                DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE, /* lastUserSetScreenBrightness= */ 0.1f,
+                /* userSetBrightnessChanged= */ false);
         when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
                 .thenReturn(0.2f);
         assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
@@ -594,7 +662,8 @@
         mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(temporaryBrightness);
         mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(true,
                 brightness, DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
-                Display.STATE_ON, mock(BrightnessConfiguration.class),
+                Display.STATE_ON, DEFAULT_USE_NORMAL_BRIGHTNESS_FOR_DOZE,
+                mock(BrightnessConfiguration.class),
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
         when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
                 autoBrightnessAdjustment);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index ee79d19..5e240cf 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -23,6 +23,7 @@
 import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE;
 import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE;
 import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE;
+import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE;
 import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
 import static com.android.server.display.mode.VotesStorage.GLOBAL_ID;
 
@@ -360,16 +361,22 @@
                 .thenReturn(true);
         init();
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(
                 Vote.forPhysicalRefreshRates(
                         MAX_REFRESH_RATE - SYNCHRONIZED_REFRESH_RATE_TOLERANCE,
                         MAX_REFRESH_RATE + SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(
+                Vote.forRenderFrameRates(
+                        MAX_REFRESH_RATE - SYNCHRONIZED_REFRESH_RATE_TOLERANCE,
+                        MAX_REFRESH_RATE + SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
 
         // Remove external display and check that sync vote is no longer present.
         mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
 
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null);
     }
 
     /** External display added, disabled feature refresh rates synchronization */
@@ -383,8 +390,10 @@
                 .thenReturn(true);
         init();
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null);
     }
 
     /** External display not applied refresh rates synchronization, because
@@ -397,8 +406,10 @@
         when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true);
         init();
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
+        assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE)).isEqualTo(null);
     }
 
     private void init() {
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 39def75..473c8c5 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -79,8 +79,6 @@
     private static final float BRIGHTNESS = 0.99f;
     private static final float BRIGHTNESS_DOZE = 0.5f;
 
-
-
     private PowerGroup mPowerGroup;
     @Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
     @Mock private Notifier mNotifier;
@@ -264,6 +262,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -282,6 +281,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -289,6 +289,7 @@
 
     @Test
     public void testUpdateWhileDozing_UpdatesDisplayPowerRequest() {
+        final boolean useNormalBrightnessForDoze = false;
         final boolean batterySaverEnabled = false;
         float brightnessFactor = 0.3f;
         PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -306,6 +307,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                useNormalBrightnessForDoze,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -323,6 +325,51 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
         assertThat(displayPowerRequest.dozeScreenBrightness).isWithin(PRECISION).of(
                 BRIGHTNESS_DOZE);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileDozing_useNormalBrightness() {
+        final boolean batterySaverEnabled = false;
+        final boolean useNormalBrightnessForDoze = true;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ null,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                useNormalBrightnessForDoze,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isWithin(PRECISION).of(
+                BRIGHTNESS_DOZE);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isTrue();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -346,6 +393,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -363,6 +411,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -385,6 +434,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ true,
@@ -402,6 +452,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -424,6 +475,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -441,6 +493,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -464,6 +517,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -481,6 +535,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -502,6 +557,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -519,6 +575,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -541,6 +598,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -558,6 +616,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
@@ -579,6 +638,7 @@
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
                 /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
                 /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
                 /* overrideDrawWakeLock= */ false,
                 powerSaveState,
                 /* quiescent= */ false,
@@ -596,6 +656,7 @@
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
         assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.useNormalBrightnessForDoze).isFalse();
         assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 40c521a..b58c28b 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1305,7 +1305,8 @@
                         Display.STATE_ON,
                         Display.STATE_REASON_DEFAULT_POLICY,
                         PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                        PowerManager.BRIGHTNESS_DEFAULT);
+                        PowerManager.BRIGHTNESS_DEFAULT,
+                        /* useDozeBrightness= */ false);
         assertTrue(isAcquired[0]);
     }
 
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index a86289b..701c350 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -272,6 +272,10 @@
         "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
 }
 
+FLAKY = [
+    "androidx.test.filters.FlakyTest",
+]
+
 FLAKY_AND_IGNORED = [
     "androidx.test.filters.FlakyTest",
     "org.junit.Ignore",
@@ -328,7 +332,7 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.recoverysystem."],
-    exclude_annotations: ["androidx.test.filters.FlakyTest"],
+    exclude_annotations: FLAKY,
 }
 
 // server pm TEST_MAPPING
@@ -357,3 +361,319 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.os."],
 }
+
+test_module_config {
+    name: "FrameworksServicesTests_presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_job_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.job"],
+    exclude_annotations: [
+        "androidx.test.filters.LargeTest",
+        "androidx.test.filters.FlakyTest",
+    ],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_job",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.job"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_tare_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.tare"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_tare",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.tare"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_usage_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.usage"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_usage",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.usage"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_battery_stats",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.am.BatteryStatsServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_accessibility_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.accessibility"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_accessibility",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.accessibility"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_binary_transparency",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.BinaryTransparencyServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_pinner_service",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.PinnerServiceTest"],
+    exclude_annotations: ["org.junit.Ignore"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_am_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.am."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_am",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.am."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_appop",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.appop"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_audio",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.audio"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_compat",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.compat"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_hdmi_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.hdmi"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_hdmi",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.hdmi"],
+    exclude_annotations: ["org.junit.Ignore"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_integrity",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.integrity."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_lights",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.lights"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_locales",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.locales."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_location_contexthub_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.location.contexthub."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_locksettings",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.locksettings."],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_logcat_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.logcat"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_logcat",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.logcat"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_net_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.net."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_om",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.om."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_pdb",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.pdb.PersistentDataBlockServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_pm_dex",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.pm.dex"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_policy_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.policy."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_policy",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.policy."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_power",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.power"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_power_hint",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.power.hint"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_powerstats",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.powerstats"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_rollback",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.rollback"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_uri",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.uri."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_location_contexthub",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.location.contexthub."],
+    include_annotations: ["android.platform.test.annotations.Postsubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_usage",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.usage"],
+    exclude_filters: ["com.android.server.usage.StorageStatsServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_soundtrigger_middleware",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.soundtrigger_middleware"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_input",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.input"],
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
index 24561c5..3d09da3 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
@@ -1,3 +1,4 @@
-# Bug component: 544200
+# Bug component: 1040349
 
-include /core/java/android/view/contentcapture/OWNERS
+include /core/java/android/view/contentprotection/OWNERS
+
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 643ee4a..62e5b9a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2007,6 +2007,25 @@
     }
 
     @Test
+    public void testCanInterruptNonRingtoneInsistentBuzzWithOtherBuzzyNotification() {
+        NotificationRecord r = getInsistentBuzzyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyVibrateLooped();
+        assertTrue(r.isInterruptive());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+        Mockito.reset(mVibrator);
+
+        // New buzzy notification stops previous looping vibration
+        NotificationRecord interrupter = getBuzzyOtherNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
+        verifyStopVibrate();
+        // And then vibrates itself
+        verifyVibrate(1);
+        assertTrue(interrupter.isInterruptive());
+        assertNotEquals(-1, interrupter.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
         NotificationChannel ringtoneChannel =
             new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
similarity index 69%
rename from services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
rename to services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index e26f3e0..8f3adba 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -22,7 +22,7 @@
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
 import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
 
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -44,7 +44,7 @@
 @Presubmit
 @MediumTest
 @RunWith(JUnitParamsRunner.class)
-public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase {
+public class KeyGestureEventTests extends ShortcutKeyTestBase {
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
@@ -56,315 +56,313 @@
     private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
     private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
     private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
-    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
-    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
 
     @Keep
     private static Object[][] shortcutTestArguments() {
-        // testName, testKeys, expectedSystemShortcut, expectedKey, expectedModifierState
+        // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState
         return new Object[][]{
                 {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_H, META_ON},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_H, META_ON},
                 {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_ENTER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER,
                         META_ON},
                 {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
                         KeyEvent.KEYCODE_RECENT_APPS, 0},
                 {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
                         META_ON},
                 {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
                         ALT_ON},
                 {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
                         KeyEvent.KEYCODE_BACK, 0},
                 {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_ESCAPE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE,
                         META_ON},
                 {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
                         META_ON},
                 {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DEL, META_ON},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON},
                 {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                         KeyEvent.KEYCODE_APP_SWITCH, 0},
                 {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                         KeyEvent.KEYCODE_ASSIST, 0},
                 {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
                         META_ON},
                 {"VOICE_ASSIST key -> Launch Voice Assistant",
                         new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
                         KeyEvent.KEYCODE_VOICE_ASSIST, 0},
                 {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
                         KeyEvent.KEYCODE_I, META_ON},
                 {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_N, META_ON},
                 {"NOTIFICATION key -> Toggle Notification Panel",
                         new int[]{KeyEvent.KEYCODE_NOTIFICATION},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_NOTIFICATION,
                         0},
                 {"Meta + Ctrl + S -> Take Screenshot",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
                         META_ON | CTRL_ON},
                 {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
                         KeyEvent.KEYCODE_SLASH, META_ON},
                 {"BRIGHTNESS_UP key -> Increase Brightness",
                         new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
                         KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
                 {"BRIGHTNESS_DOWN key -> Decrease Brightness",
                         new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
                         KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
                 {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
                         new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
                         KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
                 {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
                         new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
                         KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
                 {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
                         new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
                         KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
                 {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP,
                         KeyEvent.KEYCODE_VOLUME_UP, 0},
                 {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN,
                         KeyEvent.KEYCODE_VOLUME_DOWN, 0},
                 {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE,
                         KeyEvent.KEYCODE_VOLUME_MUTE, 0},
                 {"ALL_APPS key -> Open App Drawer in Accessibility mode",
                         new int[]{KeyEvent.KEYCODE_ALL_APPS},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_ALL_APPS, 0},
                 {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
                         KeyEvent.KEYCODE_SEARCH, 0},
                 {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
                         new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                         KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
                 {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, META_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, META_KEY,
                         META_ON},
                 {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, ALT_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY,
                         META_ON | ALT_ON},
                 {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, META_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, META_KEY,
                         META_ON | ALT_ON},
                 {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
                         KeyEvent.KEYCODE_CAPS_LOCK, 0},
                 {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
                         0},
                 {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
                         KeyEvent.KEYCODE_DPAD_UP,
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
                         KeyEvent.KEYCODE_DPAD_LEFT,
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
                         KeyEvent.KEYCODE_DPAD_RIGHT,
                         META_ON | CTRL_ON},
                 {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN, KeyEvent.KEYCODE_L,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L,
                         META_ON},
                 {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES, KeyEvent.KEYCODE_N,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N,
                         META_ON | CTRL_ON},
                 {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
                         0},
                 {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER,
                         KeyEvent.KEYCODE_TV_POWER, 0},
                 {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
                         0},
                 {"SYSTEM_NAVIGATION_UP key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
                         0},
                 {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
                         0},
                 {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
                 {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
                 {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
                         0},
                 {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
                 {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
                         KeyEvent.KEYCODE_MEDIA_PLAY, 0},
                 {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
                         KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
                 {"MEDIA_PLAY_PAUSE key -> Media Control",
                         new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
                         KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
                 {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
                         KeyEvent.KEYCODE_B, META_ON},
                 {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
                         KeyEvent.KEYCODE_EXPLORER, 0},
                 {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
                         KeyEvent.KEYCODE_C, META_ON},
                 {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
                         KeyEvent.KEYCODE_CONTACTS, 0},
                 {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
                         KeyEvent.KEYCODE_E, META_ON},
                 {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
                         KeyEvent.KEYCODE_ENVELOPE, 0},
                 {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
                         KeyEvent.KEYCODE_K, META_ON},
                 {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
                         KeyEvent.KEYCODE_CALENDAR, 0},
                 {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
                         KeyEvent.KEYCODE_P, META_ON},
                 {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
                         KeyEvent.KEYCODE_MUSIC, 0},
                 {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
                         KeyEvent.KEYCODE_U, META_ON},
                 {"CALCULATOR key -> Launch Default Calculator",
                         new int[]{KeyEvent.KEYCODE_CALCULATOR},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
                         KeyEvent.KEYCODE_CALCULATOR, 0},
                 {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
                         KeyEvent.KEYCODE_M, META_ON},
                 {"Meta + S -> Launch Default Messaging App",
                         new int[]{META_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
                         KeyEvent.KEYCODE_S, META_ON},
                 {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
                         KeyEvent.KEYCODE_DPAD_DOWN,
                         META_ON | CTRL_ON}};
     }
 
     @Keep
     private static Object[][] longPressOnHomeTestArguments() {
-        // testName, testKeys, longPressOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // testName, testKeys, longPressOnHomeBehavior, expectedKeyGestureType, expectedKey,
         // expectedModifierState
         return new Object[][]{
                 {"Long press HOME key -> Toggle Notification panel",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"Long press META + ENTER -> Toggle Notification panel",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
                         LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_ENTER,
                         META_ON},
                 {"Long press META + H -> Toggle Notification panel",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_H, META_ON},
                 {"Long press HOME key -> Launch assistant",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"Long press META + ENTER -> Launch assistant",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                         KeyEvent.KEYCODE_ENTER, META_ON},
                 {"Long press META + H -> Launch assistant",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
                         META_ON},
                 {"Long press HOME key -> Open App Drawer in Accessibility mode",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_ENTER, META_ON},
                 {"Long press META + H -> Open App Drawer in Accessibility mode",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H},
                         LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_H, META_ON}};
     }
 
     @Keep
     private static Object[][] doubleTapOnHomeTestArguments() {
-        // testName, testKeys, doubleTapOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // testName, testKeys, doubleTapOnHomeBehavior, expectedKeyGestureType, expectedKey,
         // expectedModifierState
         return new Object[][]{
                 {"Double tap HOME -> Open App switcher",
                         new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_HOME,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_HOME,
                         0},
                 {"Double tap META + ENTER -> Open App switcher",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
                         DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                         KeyEvent.KEYCODE_ENTER, META_ON},
                 {"Double tap META + H -> Open App switcher",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_H,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_H,
                         META_ON}};
     }
 
     @Keep
     private static Object[][] settingsKeyTestArguments() {
-        // testName, testKeys, settingsKeyBehavior, expectedSystemShortcut, expectedKey,
+        // testName, testKeys, settingsKeyBehavior, expectedKeyGestureType, expectedKey,
         // expectedModifierState
         return new Object[][]{
                 {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
                         SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_SETTINGS, 0}};
     }
 
@@ -387,21 +385,21 @@
     @Test
     @Parameters(method = "shortcutTestArguments")
     public void testShortcut(String testName, int[] testKeys,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
-        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+        testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
                 expectedModifierState);
     }
 
     @Test
     @Parameters(method = "longPressOnHomeTestArguments")
     public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
         sendLongPressKeyCombination(testKeys);
-        mPhoneWindowManager.assertKeyboardShortcutTriggered(
-                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+        mPhoneWindowManager.assertKeyGestureCompleted(
+                new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
 
@@ -409,23 +407,23 @@
     @Parameters(method = "doubleTapOnHomeTestArguments")
     public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
             int doubleTapOnHomeBehavior,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
         sendKeyCombination(testKeys, 0 /* duration */);
         sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertKeyboardShortcutTriggered(
-                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+        mPhoneWindowManager.assertKeyGestureCompleted(
+                new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
 
     @Test
     @Parameters(method = "settingsKeyTestArguments")
     public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
-        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+        testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
                 expectedModifierState);
     }
 
@@ -434,16 +432,16 @@
     public void testBugreportShortcutPress() {
         testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
                 new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
-                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
+                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
                 META_ON | CTRL_ON);
     }
 
     private void testShortcutInternal(String testName, int[] testKeys,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertKeyboardShortcutTriggered(
-                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+        mPhoneWindowManager.assertKeyGestureCompleted(
+                new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
 }
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 f9b5c2a..43b065d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -70,7 +70,6 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardSystemShortcut;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -804,11 +803,11 @@
         Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent());
     }
 
-    void assertKeyboardShortcutTriggered(int[] keycodes, int modifierState, int systemShortcut,
+    void assertKeyGestureCompleted(int[] keycodes, int modifierState, int gestureType,
             String errorMsg) {
         mTestLooper.dispatchAll();
-        verify(mInputManagerInternal, description(errorMsg)).notifyKeyboardShortcutTriggered(
-                anyInt(), eq(keycodes), eq(modifierState), eq(systemShortcut));
+        verify(mInputManagerInternal, description(errorMsg)).notifyKeyGestureCompleted(
+                anyInt(), eq(keycodes), eq(modifierState), eq(gestureType));
     }
 
     void assertSwitchToTask(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 14fbbe4..cb17f35f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -212,7 +212,6 @@
                 .build()
                 .getTopMostActivity();
 
-        spyOn(mActivity.mLetterboxUiController);
         spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
         doReturn(true).when(mActivity).inFreeformWindowingMode();
         doReturn(true).when(mActivity.mAppCompatController
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index a745724..1fa6868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -420,7 +420,6 @@
      */
     @CallSuper
     void onPostActivityCreation(@NonNull ActivityRecord activity) {
-        spyOn(activity.mLetterboxUiController);
         if (mOnPostActivityCreation != null) {
             mOnPostActivityCreation.accept(activity);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 84ffcb8..ba2a733 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -374,7 +374,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<CameraOverridesRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final CameraOverridesRobotTest robot = new CameraOverridesRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index c42228d..2ae23f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -20,6 +20,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 
 import android.compat.testing.PlatformCompatChangeRule;
@@ -29,7 +31,6 @@
 
 import androidx.annotation.NonNull;
 
-import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -53,18 +54,28 @@
     @Test
     public void testDisplayRotationCompatPolicy_presentWhenEnabled() {
         runTestScenario((robot) -> {
-            robot.conf().enableCameraCompatTreatmentAtBuildTime(true);
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
-            robot.checkTopActivityHasDisplayRotationCompatPolicy(true);
+            robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true);
         });
     }
 
     @Test
     public void testDisplayRotationCompatPolicy_notPresentWhenDisabled() {
         runTestScenario((robot) -> {
-            robot.conf().enableCameraCompatTreatmentAtBuildTime(false);
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false);
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
-            robot.checkTopActivityHasDisplayRotationCompatPolicy(false);
+            robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ false);
+        });
+    }
+
+    @Test
+    public void testDisplayRotationCompatPolicy_startedWhenEnabled() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true);
+            robot.checkTopActivityDisplayRotationCompatPolicyIsRunning();
         });
     }
 
@@ -72,9 +83,9 @@
     @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
     public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() {
         runTestScenario((robot) -> {
-            robot.allowEnterDesktopMode(true);
+            robot.allowEnterDesktopMode(/* isAllowed= */ true);
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
-            robot.checkTopActivityHasCameraCompatFreeformPolicy(true);
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
         });
     }
 
@@ -82,9 +93,9 @@
     @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
     public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() {
         runTestScenario((robot) -> {
-            robot.allowEnterDesktopMode(false);
+            robot.allowEnterDesktopMode(/* isAllowed= */ false);
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
-            robot.checkTopActivityHasCameraCompatFreeformPolicy(false);
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
         });
     }
 
@@ -92,9 +103,9 @@
     @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
     public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() {
         runTestScenario((robot) -> {
-            robot.allowEnterDesktopMode(true);
+            robot.allowEnterDesktopMode(/* isAllowed= */ true);
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
-            robot.checkTopActivityHasCameraCompatFreeformPolicy(false);
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
         });
     }
 
@@ -102,19 +113,86 @@
     @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
     public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() {
         runTestScenario((robot) -> {
-            robot.allowEnterDesktopMode(false);
+            robot.allowEnterDesktopMode(/* isAllowed= */ false);
             robot.activity().createActivityWithComponentInNewTaskAndDisplay();
-            robot.checkTopActivityHasCameraCompatFreeformPolicy(false);
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
+        });
+    }
+
+    @Test
+    @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testCameraCompatFreeformPolicy_startedWhenEnabledAndDW() {
+        runTestScenario((robot) -> {
+            robot.allowEnterDesktopMode(/* isAllowed= */ true);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
+            robot.checkTopActivityCameraCompatFreeformPolicyIsRunning();
+        });
+    }
+
+    @Test
+    @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testCameraStateManager_existsWhenCameraCompatFreeformExists() {
+        runTestScenario((robot) -> {
+            robot.allowEnterDesktopMode(true);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
+            robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
+        });
+    }
+
+    @Test
+    @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testCameraStateManager_startedWhenCameraCompatFreeformExists() {
+        runTestScenario((robot) -> {
+            robot.allowEnterDesktopMode(true);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ true);
+            robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
+            robot.checkTopActivityCameraStateMonitorIsRunning();
+        });
+    }
+
+    @Test
+    public void testCameraStateManager_existsWhenDisplayRotationCompatPolicyExists() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true);
+            robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
+        });
+    }
+
+    @Test
+    public void testCameraStateManager_startedWhenDisplayRotationCompatPolicyExists() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ true);
+            robot.checkTopActivityHasCameraStateMonitor(/* exists= */ true);
+            robot.checkTopActivityCameraStateMonitorIsRunning();
+        });
+    }
+
+    @Test
+    @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testCameraStateManager_doesNotExistWhenNoPolicyExists() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false);
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkTopActivityHasDisplayRotationCompatPolicy(/* exists= */ false);
+            robot.checkTopActivityHasCameraCompatFreeformPolicy(/* exists= */ false);
+            robot.checkTopActivityHasCameraStateMonitor(/* exists= */ false);
         });
     }
 
     /**
      * Runs a test scenario providing a Robot.
      */
-    void runTestScenario(@NonNull Consumer<DisplayRotationPolicyRobotTest> consumer) {
+    void runTestScenario(@NonNull Consumer<AppCompatCameraPolicyRobotTest> consumer) {
         spyOn(mWm.mAppCompatConfiguration);
-        final DisplayRotationPolicyRobotTest robot =
-                new DisplayRotationPolicyRobotTest(mWm, mAtm, mSupervisor);
+        final AppCompatCameraPolicyRobotTest robot =
+                new AppCompatCameraPolicyRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
     }
 
@@ -142,9 +220,8 @@
         });
     }
 
-    private static class DisplayRotationPolicyRobotTest extends AppCompatRobotBase {
-
-        DisplayRotationPolicyRobotTest(@NonNull WindowManagerService wm,
+    private static class AppCompatCameraPolicyRobotTest extends AppCompatRobotBase {
+        AppCompatCameraPolicyRobotTest(@NonNull WindowManagerService wm,
                 @NonNull ActivityTaskManagerService atm,
                 @NonNull ActivityTaskSupervisor supervisor) {
             super(wm, atm, supervisor);
@@ -157,17 +234,37 @@
         }
 
         void checkTopActivityHasDisplayRotationCompatPolicy(boolean exists) {
-            Assert.assertEquals(exists, activity().top().mDisplayContent
-                    .mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy());
+            assertEquals(exists, activity().top().mDisplayContent.mAppCompatCameraPolicy
+                    .hasDisplayRotationCompatPolicy());
         }
 
         void checkTopActivityHasCameraCompatFreeformPolicy(boolean exists) {
-            Assert.assertEquals(exists, activity().top().mDisplayContent
-                    .mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
+            assertEquals(exists, activity().top().mDisplayContent.mAppCompatCameraPolicy
+                    .hasCameraCompatFreeformPolicy());
+        }
+
+        void checkTopActivityHasCameraStateMonitor(boolean exists) {
+            assertEquals(exists, activity().top().mDisplayContent.mAppCompatCameraPolicy
+                    .hasCameraStateMonitor());
+        }
+
+        void checkTopActivityDisplayRotationCompatPolicyIsRunning() {
+            assertTrue(activity().top().mDisplayContent.mAppCompatCameraPolicy
+                    .mDisplayRotationCompatPolicy.isRunning());
+        }
+
+        void checkTopActivityCameraCompatFreeformPolicyIsRunning() {
+            assertTrue(activity().top().mDisplayContent.mAppCompatCameraPolicy
+                    .mCameraCompatFreeformPolicy.isRunning());
+        }
+
+        void checkTopActivityCameraStateMonitorIsRunning() {
+            assertTrue(activity().top().mDisplayContent.mAppCompatCameraPolicy
+                    .mCameraStateMonitor.isRunning());
         }
 
         void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
-            Assert.assertEquals(getTopAppCompatCameraPolicy()
+            assertEquals(getTopAppCompatCameraPolicy()
                     .isTreatmentEnabledForActivity(activity().top()), active);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
index 27c5e4e..d8f8453 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
@@ -19,8 +19,6 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
 import android.compat.testing.PlatformCompatChangeRule;
 import android.platform.test.annotations.Presubmit;
 
@@ -177,7 +175,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<FocusOverridesRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final FocusOverridesRobotTest robot = new FocusOverridesRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index f6d0744..9057b6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -514,7 +514,6 @@
      */
     void runTestScenario(boolean withActivity,
                          @NonNull Consumer<OrientationPolicyRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final OrientationPolicyRobotTest robot =
                 new OrientationPolicyRobotTest(mWm, mAtm, mSupervisor, withActivity);
         consumer.accept(robot);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
index 5ff8f02..1edbcd5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -164,7 +164,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<ReachabilityOverridesRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final ReachabilityOverridesRobotTest robot =
                 new ReachabilityOverridesRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
index 96734b3..ddc4de9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
@@ -228,7 +228,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<ReachabilityPolicyRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final ReachabilityPolicyRobotTest robot =
                 new ReachabilityPolicyRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
index cade213..b8d554b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -20,8 +20,6 @@
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
 import android.compat.testing.PlatformCompatChangeRule;
 import android.platform.test.annotations.Presubmit;
 
@@ -171,7 +169,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<ResizeOverridesRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final ResizeOverridesRobotTest robot = new ResizeOverridesRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index f2592d2..5a3ae76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -276,7 +276,6 @@
                 .setTask(mTask)
                 .build();
 
-        spyOn(mActivity.mLetterboxUiController);
         spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
         spyOn(mActivity.info);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index d223272..ad80f82 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -189,7 +189,6 @@
                 .build();
 
         spyOn(mActivity.mAtmService.getLifecycleManager());
-        spyOn(mActivity.mLetterboxUiController);
 
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ab0c8d4..ec5e51e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2656,6 +2656,7 @@
      * display.
      */
     @Test
+    @SuppressWarnings("GuardedBy")
     public void testNotResumeHomeRootTaskOnRemovingDisplay() {
         // Create a display which supports system decoration and allows reparenting root tasks to
         // another display when the display is removed.
@@ -2678,7 +2679,8 @@
         // The removed display should have no focused root task and its home root task should never
         // resume.
         assertNull(display.getFocusedRootTask());
-        verify(homeRootTask, never()).resumeTopActivityUncheckedLocked(any(), any());
+        verify(homeRootTask, never()).resumeTopActivityUncheckedLocked(
+                any(), any(), anyBoolean());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 2dea6ba..8cf593f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -597,7 +597,6 @@
                 .build();
 
         spyOn(mActivity.mAtmService.getLifecycleManager());
-        spyOn(mActivity.mLetterboxUiController);
         spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
 
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index ddadbc4..57839e2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -309,6 +309,16 @@
     public void testPublicDisplayDefaultToMoveToPrimary() {
         assertEquals(REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY,
                 mDisplayWindowSettings.getRemoveContentModeLocked(mSecondaryDisplay));
+
+        // Sets the remove-content-mode and make sure the mode is updated.
+        mDisplayWindowSettings.setRemoveContentModeLocked(mSecondaryDisplay,
+                REMOVE_CONTENT_MODE_DESTROY);
+        final int removeContentMode = mDisplayWindowSettings.getRemoveContentModeLocked(
+                mSecondaryDisplay);
+        assertEquals(REMOVE_CONTENT_MODE_DESTROY, removeContentMode);
+
+        doReturn(removeContentMode).when(mSecondaryDisplay).getRemoveContentMode();
+        assertTrue(mSecondaryDisplay.shouldDestroyContentOnRemove());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index cf321ed..8947522 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -77,7 +77,6 @@
     private ActivityRecord mActivity;
     private Task mTask;
     private DisplayContent mDisplayContent;
-    private LetterboxUiController mController;
     private AppCompatConfiguration mAppCompatConfiguration;
     private final Rect mLetterboxedPortraitTaskBounds = new Rect();
 
@@ -87,8 +86,6 @@
 
         mAppCompatConfiguration = mWm.mAppCompatConfiguration;
         spyOn(mAppCompatConfiguration);
-
-        mController = new LetterboxUiController(mWm, mActivity);
     }
 
     @Test
@@ -276,10 +273,13 @@
         final Resources resources = mWm.mContext.getResources();
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
 
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
+                .getAppCompatAspectRatioPolicy();
+
         mainWindow.mInvGlobalScale = 1f;
         spyOn(resources);
         spyOn(mActivity);
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        spyOn(aspectRatioPolicy);
 
         if (taskbar != null) {
             taskbar.setVisible(true);
@@ -288,8 +288,8 @@
         doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
         doReturn(false).when(mActivity).isInLetterboxAnimation();
         doReturn(true).when(mActivity).isVisible();
-        doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
+        doReturn(true).when(aspectRatioPolicy)
+                .isLetterboxedForFixedOrientationAndAspectRatio();
         doReturn(insets).when(mainWindow).getInsetsState();
         doReturn(attrs).when(mainWindow).getAttrs();
         doReturn(true).when(mainWindow).isDrawn();
@@ -300,9 +300,6 @@
         doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize(
                 R.dimen.taskbar_frame_height);
 
-        // Need to reinitialise due to the change in resources getDimensionPixelSize output.
-        mController = new LetterboxUiController(mWm, mActivity);
-
         return mainWindow;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index d29505f..84c2c32 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -54,6 +54,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.refEq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -598,7 +599,7 @@
         mRootWindowContainer.applySleepTokens(true);
         verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleeping();
         verify(task, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
-                null /* target */, null /* targetOptions */);
+                isNull() /* target */, isNull() /* targetOptions */, eq(false) /* deferPause */);
     }
 
     @Test
@@ -790,8 +791,7 @@
         doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
 
         // Use the task as target to resume.
-        mRootWindowContainer.resumeFocusedTasksTopActivities(rootTask, activity,
-                null /* targetOptions */);
+        mRootWindowContainer.resumeFocusedTasksTopActivities(rootTask, activity);
 
         // Verify the target task should resume its activity.
         verify(rootTask, times(1)).resumeTopActivityUncheckedLocked(
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 36696a1..65e3baa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3017,6 +3017,7 @@
     }
 
     @Test
+    @SuppressWarnings("GuardedBy")
     public void testDisplayIgnoreOrientationRequest_pausedAppNotLostSizeCompat() {
         // Set up a display in landscape and ignoring orientation request.
         setUpDisplaySizeWithApp(2800, 1400);
@@ -3043,7 +3044,8 @@
         assertActivityMaxBoundsSandboxed();
 
         final Rect activityBounds = new Rect(mActivity.getBounds());
-        mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
+        mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */,
+                false /* deferPause */);
 
         // App still in size compat, and the bounds don't change.
         verify(mActivity, never()).clearSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 18255b8..f56825f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -265,7 +265,7 @@
         rootTask.setTaskOrganizer(null);
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
 
-        verify(mWm.mAtmService).removeTask(eq(rootTask.mTaskId));
+        verify(mWm.mAtmService).removeTask(eq(rootTask));
     }
 
     @Test
@@ -283,7 +283,7 @@
         rootTask.setTaskOrganizer(null);
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
 
-        verify(mWm.mAtmService, never()).removeTask(eq(rootTask.mTaskId));
+        verify(mWm.mAtmService, never()).removeTask(eq(rootTask));
     }
 
     @Test
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 65e8e13..ddf5ef2 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -463,8 +463,9 @@
     public String toString() {
         return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
                 + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
-                + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() +
-                "  mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + ")";
+                + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList()
+                + ", mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo
+                + getCarrierRestrictionStatusToLog() + ")";
     }
 
     private String getCarrierInfoList() {
@@ -476,6 +477,13 @@
         }
     }
 
+    private String getCarrierRestrictionStatusToLog() {
+        if(android.os.Build.isDebuggable()) {
+            return ", CarrierRestrictionStatus = " + mCarrierRestrictionStatus;
+        }
+        return "";
+    }
+
     /**
      * Builder for a {@link CarrierRestrictionRules}.
      */
diff --git a/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl
index f981fb1..5f0d986 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl
@@ -16,6 +16,8 @@
 
 package android.telephony.satellite;
 
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
+
 /**
  * Interface for satellite provision state callback.
  * @hide
@@ -27,4 +29,14 @@
      * @param provisioned True means the service is provisioned and false means it is not.
      */
     void onSatelliteProvisionStateChanged(in boolean provisioned);
+
+    /**
+     * Called when the provisioning state of one or more SatelliteSubscriberInfos changes.
+     *
+     * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states of
+     * the SatelliteSubscriberInfos.
+     * @hide
+     */
+    void onSatelliteSubscriptionProvisionStateChanged(in List<SatelliteSubscriberProvisionStatus>
+        satelliteSubscriberProvisionStatus);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e657d7f..0c98327 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1404,6 +1404,16 @@
                                         () -> callback.onSatelliteProvisionStateChanged(
                                                 provisioned)));
                             }
+
+                            @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+                            @Override
+                            public void onSatelliteSubscriptionProvisionStateChanged(
+                                    @NonNull List<SatelliteSubscriberProvisionStatus>
+                                            satelliteSubscriberProvisionStatus) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onSatelliteSubscriptionProvisionStateChanged(
+                                                satelliteSubscriberProvisionStatus)));
+                            }
                         };
                 sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteProvisionStateChanged(
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index a12952b..e8ae0f5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -17,10 +17,13 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
 import com.android.internal.telephony.flags.Flags;
 
+import java.util.List;
+
 /**
  * A callback class for monitoring satellite provision state change events.
  *
@@ -39,4 +42,16 @@
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteProvisionStateChanged(boolean provisioned);
+
+    /**
+     * Called when the provisioning state of one or more SatelliteSubscriberInfos changes.
+     *
+     * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states
+     *                                           of the SatelliteSubscriberInfos.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    default void onSatelliteSubscriptionProvisionStateChanged(
+            @NonNull List<SatelliteSubscriberProvisionStatus>
+                    satelliteSubscriberProvisionStatus) {};
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl
new file mode 100644
index 0000000..80de779
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony.satellite;
+
+parcelable SatelliteSubscriberProvisionStatus;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
new file mode 100644
index 0000000..e3d619e
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
@@ -0,0 +1,174 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Represents the provisioning state of SatelliteSubscriberInfo.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public class SatelliteSubscriberProvisionStatus implements Parcelable {
+    private SatelliteSubscriberInfo mSubscriberInfo;
+    /** {@code true} mean the satellite subscriber is provisioned, {@code false} otherwise. */
+    private boolean mProvisionStatus;
+
+    public SatelliteSubscriberProvisionStatus(@NonNull Builder builder) {
+        mSubscriberInfo = builder.mSubscriberInfo;
+        mProvisionStatus = builder.mProvisionStatus;
+    }
+
+    /**
+     * Builder class for constructing SatelliteSubscriberProvisionStatus objects
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static class Builder {
+        private SatelliteSubscriberInfo mSubscriberInfo;
+        private boolean mProvisionStatus;
+
+        /**
+         * Set the SatelliteSubscriberInfo and returns the Builder class.
+         * @hide
+         */
+        public Builder setSatelliteSubscriberInfo(SatelliteSubscriberInfo satelliteSubscriberInfo) {
+            mSubscriberInfo = satelliteSubscriberInfo;
+            return this;
+        }
+
+        /**
+         * Set the SatelliteSubscriberInfo's provisionStatus and returns the Builder class.
+         * @hide
+         */
+        @NonNull
+        public Builder setProvisionStatus(boolean provisionStatus) {
+            mProvisionStatus = provisionStatus;
+            return this;
+        }
+
+        /**
+         * Returns SatelliteSubscriberProvisionStatus object.
+         * @hide
+         */
+        @NonNull
+        public SatelliteSubscriberProvisionStatus build() {
+            return new SatelliteSubscriberProvisionStatus(this);
+        }
+    }
+
+    private SatelliteSubscriberProvisionStatus(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        mSubscriberInfo.writeToParcel(out, flags);
+        out.writeBoolean(mProvisionStatus);
+    }
+
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final @android.annotation.NonNull Creator<SatelliteSubscriberProvisionStatus>
+            CREATOR =
+            new Creator<SatelliteSubscriberProvisionStatus>() {
+                @Override
+                public SatelliteSubscriberProvisionStatus createFromParcel(Parcel in) {
+                    return new SatelliteSubscriberProvisionStatus(in);
+                }
+
+                @Override
+                public SatelliteSubscriberProvisionStatus[] newArray(int size) {
+                    return new SatelliteSubscriberProvisionStatus[size];
+                }
+            };
+
+    /**
+     * @hide
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * SatelliteSubscriberInfo that has a provisioning state.
+     * @return SatelliteSubscriberInfo.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public @NonNull SatelliteSubscriberInfo getSatelliteSubscriberInfo() {
+        return mSubscriberInfo;
+    }
+
+    /**
+     * SatelliteSubscriberInfo's provisioning state.
+     * @return {@code true} means provisioning. {@code false} means deprovisioning.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public @NonNull  boolean getProvisionStatus() {
+        return mProvisionStatus;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("SatelliteSubscriberInfo:");
+        sb.append(mSubscriberInfo);
+        sb.append(",");
+
+        sb.append("ProvisionStatus:");
+        sb.append(mProvisionStatus);
+        return sb.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSubscriberInfo, mProvisionStatus);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SatelliteSubscriberProvisionStatus)) return false;
+        SatelliteSubscriberProvisionStatus that = (SatelliteSubscriberProvisionStatus) o;
+        return Objects.equals(mSubscriberInfo, that.mSubscriberInfo)
+                && mProvisionStatus == that.mProvisionStatus;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mSubscriberInfo = in.readParcelable(SatelliteSubscriberInfo.class.getClassLoader(),
+                SatelliteSubscriberInfo.class);
+        mProvisionStatus = in.readBoolean();
+    }
+}
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
new file mode 100644
index 0000000..14aac66
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 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.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+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.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyGestureEventListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureEventListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyGestureEventListenerTest {
+
+    companion object {
+        const val DEVICE_ID = 1
+        val HOME_GESTURE_EVENT = KeyGestureEvent(
+            DEVICE_ID,
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = SetFlagsRule()
+
+    private val testLooper = TestLooper()
+    private val executor = HandlerExecutor(Handler(testLooper.looper))
+    private var registeredListener: IKeyGestureEventListener? = null
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle key gesture event listener registration.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyGestureEventListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered key gesture event listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerKeyGestureEventListener(any())
+
+        // Handle key gesture event listener being unregistered.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyGestureEventListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            registeredListener = null
+            null
+        }.`when`(iInputManagerMock).unregisterKeyGestureEventListener(any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyKeyGestureEvent(event: KeyGestureEvent) {
+        registeredListener!!.onKeyGestureEvent(
+            event.deviceId,
+            event.keycodes,
+            event.modifierState,
+            event.keyGestureType
+        )
+    }
+
+    @Test
+    fun testListenerHasCorrectGestureNotified() {
+        var callbackCount = 0
+
+        // Add a key gesture event listener
+        inputManager.registerKeyGestureEventListener(executor) {
+            event: KeyGestureEvent ->
+            assertEquals(HOME_GESTURE_EVENT, event)
+            callbackCount++
+        }
+
+        // Notifying key gesture event will notify the listener.
+        notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testAddingListenersRegistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyGestureEventListener { _ -> }
+        val callback2 = InputManager.KeyGestureEventListener { _ -> }
+
+        assertNull(registeredListener)
+
+        // Adding the listener should register the callback with InputManagerService.
+        inputManager.registerKeyGestureEventListener(executor, callback1)
+        assertNotNull(registeredListener)
+
+        // Adding another listener should not register new internal listener.
+        val currListener = registeredListener
+        inputManager.registerKeyGestureEventListener(executor, callback2)
+        assertEquals(currListener, registeredListener)
+    }
+
+    @Test
+    fun testRemovingListenersUnregistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyGestureEventListener { _ -> }
+        val callback2 = InputManager.KeyGestureEventListener { _ -> }
+
+        inputManager.registerKeyGestureEventListener(executor, callback1)
+        inputManager.registerKeyGestureEventListener(executor, callback2)
+
+        // Only removing all listeners should remove the internal callback
+        inputManager.unregisterKeyGestureEventListener(callback1)
+        assertNotNull(registeredListener)
+        inputManager.unregisterKeyGestureEventListener(callback2)
+        assertNull(registeredListener)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.KeyGestureEventListener { _ -> callbackCount1++ }
+        val callback2 = InputManager.KeyGestureEventListener { _ -> callbackCount2++ }
+
+        // Add both key gesture event listeners
+        inputManager.registerKeyGestureEventListener(executor, callback1)
+        inputManager.registerKeyGestureEventListener(executor, callback2)
+
+        // Notifying key gesture event, should notify both the callbacks.
+        notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+        testLooper.dispatchAll()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        inputManager.unregisterKeyGestureEventListener(callback2)
+        // Notifying key gesture event, should still trigger callback1 but not
+        // callback2.
+        notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+        testLooper.dispatchAll()
+        assertEquals(2, callbackCount1)
+        assertEquals(1, callbackCount2)
+    }
+}
diff --git a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
deleted file mode 100644
index 24d7291..0000000
--- a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 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.hardware.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.os.Handler
-import android.os.HandlerExecutor
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.KeyEvent
-import androidx.test.core.app.ApplicationProvider
-import com.android.server.testutils.any
-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.Mockito
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnitRunner
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.fail
-
-/**
- * Tests for [InputManager.KeyboardSystemShortcutListener].
- *
- * Build/Install/Run:
- * atest InputTests:KeyboardSystemShortcutListenerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner::class)
-class KeyboardSystemShortcutListenerTest {
-
-    companion object {
-        const val DEVICE_ID = 1
-        val HOME_SHORTCUT = KeyboardSystemShortcut(
-            intArrayOf(KeyEvent.KEYCODE_H),
-            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
-            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
-        )
-    }
-
-    @get:Rule
-    val rule = SetFlagsRule()
-
-    private val testLooper = TestLooper()
-    private val executor = HandlerExecutor(Handler(testLooper.looper))
-    private var registeredListener: IKeyboardSystemShortcutListener? = null
-    private lateinit var context: Context
-    private lateinit var inputManager: InputManager
-    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
-
-    @Mock
-    private lateinit var iInputManagerMock: IInputManager
-
-    @Before
-    fun setUp() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
-        inputManager = InputManager(context)
-        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
-                .thenReturn(inputManager)
-
-        // Handle keyboard system shortcut listener registration.
-        doAnswer {
-            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
-            if (registeredListener != null &&
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                // There can only be one registered keyboard system shortcut listener per process.
-                fail("Trying to register a new listener when one already exists")
-            }
-            registeredListener = listener
-            null
-        }.`when`(iInputManagerMock).registerKeyboardSystemShortcutListener(any())
-
-        // Handle keyboard system shortcut listener being unregistered.
-        doAnswer {
-            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
-            if (registeredListener == null ||
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                fail("Trying to unregister a listener that is not registered")
-            }
-            registeredListener = null
-            null
-        }.`when`(iInputManagerMock).unregisterKeyboardSystemShortcutListener(any())
-    }
-
-    @After
-    fun tearDown() {
-        if (this::inputManagerGlobalSession.isInitialized) {
-            inputManagerGlobalSession.close()
-        }
-    }
-
-    private fun notifyKeyboardSystemShortcutTriggered(id: Int, shortcut: KeyboardSystemShortcut) {
-        registeredListener!!.onKeyboardSystemShortcutTriggered(
-            id,
-            shortcut.keycodes,
-            shortcut.modifierState,
-            shortcut.systemShortcut
-        )
-    }
-
-    @Test
-    fun testListenerHasCorrectSystemShortcutNotified() {
-        var callbackCount = 0
-
-        // Add a keyboard system shortcut listener
-        inputManager.registerKeyboardSystemShortcutListener(executor) {
-            deviceId: Int, systemShortcut: KeyboardSystemShortcut ->
-            assertEquals(DEVICE_ID, deviceId)
-            assertEquals(HOME_SHORTCUT, systemShortcut)
-            callbackCount++
-        }
-
-        // Notifying keyboard system shortcut triggered will notify the listener.
-        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount)
-    }
-
-    @Test
-    fun testAddingListenersRegistersInternalCallbackListener() {
-        // Set up two callbacks.
-        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-
-        assertNull(registeredListener)
-
-        // Adding the listener should register the callback with InputManagerService.
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
-        assertNotNull(registeredListener)
-
-        // Adding another listener should not register new internal listener.
-        val currListener = registeredListener
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
-        assertEquals(currListener, registeredListener)
-    }
-
-    @Test
-    fun testRemovingListenersUnregistersInternalCallbackListener() {
-        // Set up two callbacks.
-        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
-
-        // Only removing all listeners should remove the internal callback
-        inputManager.unregisterKeyboardSystemShortcutListener(callback1)
-        assertNotNull(registeredListener)
-        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
-        assertNull(registeredListener)
-    }
-
-    @Test
-    fun testMultipleListeners() {
-        // Set up two callbacks.
-        var callbackCount1 = 0
-        var callbackCount2 = 0
-        val callback1 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount1++ }
-        val callback2 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount2++ }
-
-        // Add both keyboard system shortcut listeners
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
-
-        // Notifying keyboard system shortcut triggered, should notify both the callbacks.
-        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        testLooper.dispatchAll()
-        assertEquals(1, callbackCount1)
-        assertEquals(1, callbackCount2)
-
-        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
-        // Notifying keyboard system shortcut triggered, should still trigger callback1 but not
-        // callback2.
-        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        testLooper.dispatchAll()
-        assertEquals(2, callbackCount1)
-        assertEquals(1, callbackCount2)
-    }
-}
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
new file mode 100644
index 0000000..3f611e0
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IKeyGestureEventListener
+import android.hardware.input.KeyGestureEvent
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link KeyGestureController}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureControllerTests
+ */
+@Presubmit
+class KeyGestureControllerTests {
+
+    companion object {
+        val DEVICE_ID = 1
+        val HOME_GESTURE_EVENT = KeyGestureEvent(
+            DEVICE_ID,
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var keyGestureController: KeyGestureController
+    private lateinit var context: Context
+    private var lastEvent: KeyGestureEvent? = null
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        keyGestureController = KeyGestureController()
+    }
+
+    @Test
+    fun testKeyGestureEvent_registerUnregisterListener() {
+        val listener = KeyGestureEventListener()
+
+        // Register key gesture event listener
+        keyGestureController.registerKeyGestureEventListener(listener, 0)
+        keyGestureController.onKeyGestureEvent(HOME_GESTURE_EVENT)
+        assertEquals(
+            "Listener should get callback on key gesture event",
+            HOME_GESTURE_EVENT,
+            lastEvent!!
+        )
+
+        // Unregister listener
+        lastEvent = null
+        keyGestureController.unregisterKeyGestureEventListener(listener, 0)
+        keyGestureController.onKeyGestureEvent(HOME_GESTURE_EVENT)
+        assertNull("Listener should not get callback after being unregistered", lastEvent)
+    }
+
+    inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() {
+        override fun onKeyGestureEvent(
+                deviceId: Int,
+                keycodes: IntArray,
+                modifierState: Int,
+                gestureType: Int
+        ) {
+            lastEvent = KeyGestureEvent(deviceId, keycodes, modifierState, gestureType)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
deleted file mode 100644
index 5a40a1c..0000000
--- a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 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.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.hardware.input.IKeyboardSystemShortcutListener
-import android.hardware.input.KeyboardSystemShortcut
-import android.platform.test.annotations.Presubmit
-import android.view.KeyEvent
-import androidx.test.core.app.ApplicationProvider
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
-
-/**
- * Tests for {@link KeyboardShortcutCallbackHandler}.
- *
- * Build/Install/Run:
- * atest InputTests:KeyboardShortcutCallbackHandlerTests
- */
-@Presubmit
-class KeyboardShortcutCallbackHandlerTests {
-
-    companion object {
-        val DEVICE_ID = 1
-        val HOME_SHORTCUT = KeyboardSystemShortcut(
-            intArrayOf(KeyEvent.KEYCODE_H),
-            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
-            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
-        )
-    }
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    private lateinit var keyboardShortcutCallbackHandler: KeyboardShortcutCallbackHandler
-    private lateinit var context: Context
-    private var lastShortcut: KeyboardSystemShortcut? = null
-
-    @Before
-    fun setup() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        keyboardShortcutCallbackHandler = KeyboardShortcutCallbackHandler()
-    }
-
-    @Test
-    fun testKeyboardSystemShortcutTriggered_registerUnregisterListener() {
-        val listener = KeyboardSystemShortcutListener()
-
-        // Register keyboard system shortcut listener
-        keyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, 0)
-        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        assertEquals(
-            "Listener should get callback on keyboard system shortcut triggered",
-            HOME_SHORTCUT,
-            lastShortcut!!
-        )
-
-        // Unregister listener
-        lastShortcut = null
-        keyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, 0)
-        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        assertNull("Listener should not get callback after being unregistered", lastShortcut)
-    }
-
-    inner class KeyboardSystemShortcutListener : IKeyboardSystemShortcutListener.Stub() {
-        override fun onKeyboardSystemShortcutTriggered(
-                deviceId: Int,
-                keycodes: IntArray,
-                modifierState: Int,
-                shortcut: Int
-        ) {
-            assertEquals(DEVICE_ID, deviceId)
-            lastShortcut = KeyboardSystemShortcut(keycodes, modifierState, shortcut)
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 5a48327..9657225 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -214,6 +214,13 @@
         verify(mReader, never()).getViewerString(anyLong());
     }
 
+    @Test
+    public void loadViewerConfigOnLogcatGroupRegistration() {
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        mProtoLog.registerGroups(TestProtoLogGroup.TEST_GROUP);
+        verify(mReader).loadViewerConfig(any(), any());
+    }
+
     private static class ProtoLogData {
         Long mMessageHash = null;
         Long mElapsedTime = null;
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
index 359eb35..5012c23 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -84,6 +84,7 @@
         content.addView(enableSyncButton,
                 new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                         ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM));
+        content.setFitsSystemWindows(true);
         setContentView(content);
 
         mSv.setZOrderOnTop(false);
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
index 73e0163..4119ea2 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
@@ -37,6 +37,7 @@
 
     protected void onCreate(Bundle savedInstanceState) {
         FrameLayout content = new FrameLayout(this);
+        content.setFitsSystemWindows(true);
         super.onCreate(savedInstanceState);
         mView = new SurfaceView(this);
         content.addView(mView, new FrameLayout.LayoutParams(
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
index ac7dc9e..5287068 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -88,6 +88,7 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         LinearLayout content = new LinearLayout(this);
+        content.setFitsSystemWindows(true);
         mLocalSurfaceView = new SurfaceView(this);
         content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
                 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
diff --git a/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java b/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
index 09236ff..459db8a 100644
--- a/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
@@ -74,6 +74,9 @@
         }
     }
 
+    private ObjectAnimator mColorValueAnimator;
+    private ObjectAnimator mYAnimator;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -81,16 +84,28 @@
 
         // animate color to force bitmap uploads
         UploadView uploadView = findViewById(R.id.upload_view);
-        ObjectAnimator colorValueAnimator = ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
-        colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
-        colorValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
-        colorValueAnimator.start();
+        mColorValueAnimator = ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
+        mColorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mColorValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        mColorValueAnimator.start();
 
         // animate scene root to guarantee there's a minimum amount of GPU rendering work
         View uploadRoot = findViewById(R.id.upload_root);
-        ObjectAnimator yAnimator = ObjectAnimator.ofFloat(uploadRoot, "translationY", 0, 100);
-        yAnimator.setRepeatMode(ValueAnimator.REVERSE);
-        yAnimator.setRepeatCount(ValueAnimator.INFINITE);
-        yAnimator.start();
+        mYAnimator = ObjectAnimator.ofFloat(uploadRoot, "translationY", 0, 100);
+        mYAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mYAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        mYAnimator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mColorValueAnimator != null) {
+            mColorValueAnimator.cancel();
+        }
+
+        if (mYAnimator != null) {
+            mYAnimator.cancel();
+        }
     }
 }
diff --git a/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java b/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java
index 882163b..9d10f76 100644
--- a/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java
@@ -66,18 +66,29 @@
             return PixelFormat.OPAQUE;
         }
     }
+
+    private ObjectAnimator mObjectAnimator;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         OverdrawDrawable overdraw = new OverdrawDrawable();
         getWindow().setBackgroundDrawable(overdraw);
-
         setContentView(new View(this));
 
-        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(overdraw, "colorValue", 0, 255);
-        objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
-        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
-        objectAnimator.start();
+        mObjectAnimator = ObjectAnimator.ofInt(overdraw, "colorValue", 0, 255);
+        mObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        mObjectAnimator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mObjectAnimator != null) {
+            mObjectAnimator.cancel();
+        }
     }
 }
diff --git a/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java b/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
index b26a660..1b28dc2 100644
--- a/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
@@ -33,6 +33,7 @@
 public class GlTextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
     private ImageFlipRenderThread mRenderThread;
     private TextureView mTextureView;
+    private ObjectAnimator mAnimator;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -54,17 +55,17 @@
         int distance = Math.max(mTextureView.getWidth(), mTextureView.getHeight());
         mTextureView.setCameraDistance(distance * metrics.density);
 
-        ObjectAnimator animator = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
-        animator.setRepeatMode(ObjectAnimator.REVERSE);
-        animator.setRepeatCount(ObjectAnimator.INFINITE);
-        animator.setDuration(4000);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+        mAnimator = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
+        mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
+        mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+        mAnimator.setDuration(4000);
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 mTextureView.invalidate();
             }
         });
-        animator.start();
+        mAnimator.start();
     }
 
     @Override
@@ -86,4 +87,11 @@
     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
     }
 
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java b/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
index 76ed1ae..f1e96c8 100644
--- a/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
@@ -51,6 +51,7 @@
     }
 
     private ColorView[][] mColorViews;
+    private ObjectAnimator mAnimator;
 
     @SuppressWarnings("unused")
     public void setColorValue(int colorValue) {
@@ -80,9 +81,17 @@
             }
         }
 
-        ObjectAnimator animator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
-        animator.setRepeatMode(ValueAnimator.REVERSE);
-        animator.setRepeatCount(ValueAnimator.INFINITE);
-        animator.start();
+        mAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
+        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        mAnimator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
     }
 }
diff --git a/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java b/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java
index 804ced1..9563572 100644
--- a/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java
@@ -33,6 +33,7 @@
     private final ArrayList<LinearLayout> mLayouts = new ArrayList<>();
 
     private int mColorToggle = 0;
+    private ObjectAnimator mAnimator;
 
     private void createQuadTree(LinearLayout parent, int remainingDepth) {
         mLayouts.add(parent);
@@ -71,9 +72,17 @@
         createQuadTree(root, 8);
         setContentView(root);
 
-        ObjectAnimator animator = ObjectAnimator.ofInt(this, "ignoredValue", 0, 1000);
-        animator.setRepeatMode(ValueAnimator.REVERSE);
-        animator.setRepeatCount(ValueAnimator.INFINITE);
-        animator.start();
+        mAnimator = ObjectAnimator.ofInt(this, "ignoredValue", 0, 1000);
+        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        mAnimator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
     }
 }