Merge "[SB][Screen Chips] Add CUJ java mappings for launching chip dialog." into main
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 762e2af..0750c8d 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -145,7 +145,9 @@
                 Intent.ACTION_USER_STARTED,
                 Intent.ACTION_MEDIA_MOUNTED,
                 Intent.ACTION_USER_UNLOCKED,
-                Intent.ACTION_USER_STOPPED);
+                Intent.ACTION_USER_STOPPED,
+                Intent.ACTION_PROFILE_AVAILABLE,
+                Intent.ACTION_PROFILE_UNAVAILABLE);
         mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
         removeAnyPreviousTestUsers();
         if (mAm.getCurrentUser() != UserHandle.USER_SYSTEM) {
@@ -1230,6 +1232,255 @@
         }
     }
 
+    /** Tests creating a private profile. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void createPrivateProfile() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            Log.i(TAG, "Starting timer");
+
+            final int userId = createPrivateProfileUser();
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            waitCoolDownPeriod();
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests creating and starting a newly created private profile. This test waits for the
+     * {@link Intent.ACTION_USER_STARTED} to be received.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void createAndStartPrivateProfile_realistic() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            Log.i(TAG, "Starting timer");
+            final int userId = createPrivateProfileUser();
+
+            // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
+            // ACTION_USER_STARTED.
+            runThenWaitForBroadcasts(userId, () -> {
+                mIam.startUserInBackground(userId);
+            }, Intent.ACTION_USER_STARTED);
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            waitCoolDownPeriod();
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /** Tests for stopping the private profile. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileStopped() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            runThenWaitForBroadcasts(userId, () -> {
+                startUserInBackgroundAndWaitForUnlock(userId);
+            }, Intent.ACTION_MEDIA_MOUNTED);
+
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            stopUser(userId);
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests unlocking a newly-created private profile using the
+     * {@link UserManager#requestQuietModeEnabled} api.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileUnlock() throws RemoteException {
+
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            startUserInBackgroundAndWaitForUnlock(userId);
+            mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+            waitCoolDownPeriod();
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            mUm.requestQuietModeEnabled(false, UserHandle.of(userId));
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests unlocking a newly-created private profile using the
+     * {@link UserManager#requestQuietModeEnabled} api and waiting for the
+     * {@link Intent.ACTION_PROFILE_AVAILABLE} to be received.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileUnlock_realistic() throws RemoteException {
+
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            startUserInBackgroundAndWaitForUnlock(userId);
+            mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+            waitCoolDownPeriod();
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            runThenWaitForBroadcasts(userId, () -> {
+                mUm.requestQuietModeEnabled(false, UserHandle.of(userId));
+            }, Intent.ACTION_PROFILE_AVAILABLE);
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests locking a newly-created private profile using the
+     * {@link UserManager#requestQuietModeEnabled} api.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileLock() throws RemoteException {
+
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            startUserInBackgroundAndWaitForUnlock(userId);
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests locking a newly-created private profile using the
+     * {@link UserManager#requestQuietModeEnabled} api and waiting for the
+     * {@link Intent.ACTION_PROFILE_UNAVAILABLE} to be received.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileLock_realistic() throws RemoteException {
+
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            startUserInBackgroundAndWaitForUnlock(userId);
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            runThenWaitForBroadcasts(userId, () -> {
+                mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+            }, Intent.ACTION_PROFILE_UNAVAILABLE);
+
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /** Tests removing a newly-created private profile */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileRemove() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+            removeUser(userId);
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /** Tests installing a pre-existing app in a private profile. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileInstall() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            startUserInBackgroundAndWaitForUnlock(userId);
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            waitCoolDownPeriod();
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests creating a new private profile, starting it, installing an app, and launching that app
+     * in it.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileCreateStartInstallAndLaunchApp() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            final int userId = createPrivateProfileUser();
+            startUserInBackgroundAndWaitForUnlock(userId);
+            installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+            startApp(userId, DUMMY_PACKAGE_NAME);
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            waitCoolDownPeriod();
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
+    /**
+     * Tests starting & launching an already-installed app in a private profile.
+     */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void privateProfileStartAndLaunchApp() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int userId = createPrivateProfileUser();
+            WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+            installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+            mRunner.resumeTiming();
+            Log.i(TAG, "Starting timer");
+
+            startUserInBackgroundAndWaitForUnlock(userId);
+            startApp(userId, DUMMY_PACKAGE_NAME);
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            waitCoolDownPeriod();
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
     /** Creates a new user, returning its userId. */
     private int createUserNoFlags() {
         return createUserWithFlags(/* flags= */ 0);
@@ -1252,6 +1503,18 @@
         return userInfo.id;
     }
 
+    /** Creates a private profile under the current user, returning its userId. */
+    private int createPrivateProfileUser() {
+        final UserInfo userInfo = mUm.createProfileForUser(TEST_USER_NAME,
+                UserManager.USER_TYPE_PROFILE_PRIVATE, /* flags */ 0, mAm.getCurrentUser());
+        attestFalse(
+                "Creating a private profile failed. Most likely there is already a pre-existing "
+                        + "private profile on the device.",
+                userInfo == null);
+        mUsersToRemove.add(userInfo.id);
+        return userInfo.id;
+    }
+
     /**
      * Start user in background and wait for it to unlock by waiting for
      * UserState.mUnlockProgress.finish().
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 5efda98..bf67187 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -75,10 +75,10 @@
 fun getClassFlag(classItem: ClassItem): String? {
     var classFlag = getFlagAnnotation(classItem)
     var cur = classItem
-    // If a class is not an inner class, use its @FlaggedApi annotation value.
+    // If a class is not a nested class, use its @FlaggedApi annotation value.
     // Otherwise, use the flag value of the closest outer class that is annotated by @FlaggedApi.
-    while (cur.isInnerClass() && classFlag == null) {
-        cur = cur.parent() as ClassItem
+    while (classFlag == null) {
+        cur = cur.containingClass() ?: break
         classFlag = getFlagAnnotation(cur)
     }
     return classFlag
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 40d4fb6..1767d64 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -32,6 +32,7 @@
 import android.os.CombinedVibration;
 import android.hardware.input.IInputSensorEventListener;
 import android.hardware.input.InputSensorInfo;
+import android.hardware.input.KeyGlyphMap;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
 import android.os.IBinder;
@@ -236,4 +237,6 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
     void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
+
+    KeyGlyphMap getKeyGlyphMap(int deviceId);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 21c9002..23e262c 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,6 +19,7 @@
 import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
 import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
 import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
+import static com.android.hardware.input.Flags.keyboardGlyphMap;
 
 import android.Manifest;
 import android.annotation.FlaggedApi;
@@ -158,6 +159,34 @@
             "android.hardware.input.metadata.KEYBOARD_LAYOUTS";
 
     /**
+     * Broadcast Action: Query available keyboard glyph maps.
+     * <p>
+     * The input manager service locates available keyboard glyph maps
+     * by querying broadcast receivers that are registered for this action.
+     * An application can offer additional keyboard glyph maps to the user
+     * by declaring a suitable broadcast receiver in its manifest.
+     * </p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_QUERY_KEYBOARD_GLYPH_MAPS =
+            "android.hardware.input.action.QUERY_KEYBOARD_GLYPH_MAPS";
+
+    /**
+     * Metadata Key: Keyboard glyph map metadata associated with
+     * {@link #ACTION_QUERY_KEYBOARD_GLYPH_MAPS}.
+     * <p>
+     * Specifies the resource id of a XML resource that describes the keyboard
+     * glyph maps that are provided by the application.
+     * </p>
+     *
+     * @hide
+     */
+    public static final String META_DATA_KEYBOARD_GLYPH_MAPS =
+            "android.hardware.input.metadata.KEYBOARD_GLYPH_MAPS";
+
+    /**
      * Prevent touches from being consumed by apps if these touches passed through a non-trusted
      * window from a different UID and are considered unsafe.
      *
@@ -898,6 +927,23 @@
     }
 
     /**
+     * Provides associated glyph map for the keyboard device (if available)
+     *
+     * @hide
+     */
+    @Nullable
+    public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+        if (!keyboardGlyphMap()) {
+            return null;
+        }
+        try {
+            return mIm.getKeyGlyphMap(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Injects an input event into the event system, targeting windows owned by the provided uid.
      *
      * If a valid targetUid is provided, the system will only consider injecting the input event
diff --git a/core/java/android/hardware/input/KeyGlyphMap.aidl b/core/java/android/hardware/input/KeyGlyphMap.aidl
new file mode 100644
index 0000000..104f1e4
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGlyphMap.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.hardware.input;
+
+parcelable KeyGlyphMap;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/KeyGlyphMap.java b/core/java/android/hardware/input/KeyGlyphMap.java
new file mode 100644
index 0000000..49c47a2
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGlyphMap.java
@@ -0,0 +1,187 @@
+/*
+ * 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.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class provides access to device specific key glyphs, modifier glyphs and device specific
+ * shortcuts and keys
+ *
+ * @hide
+ */
+public final class KeyGlyphMap implements Parcelable {
+    private static final String TAG = "KeyGlyphMap";
+
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final SparseIntArray mKeyGlyphs;
+    @NonNull
+    private final SparseIntArray mModifierGlyphs;
+    @NonNull
+    private final int[] mFunctionRowKeys;
+    @NonNull
+    private final Map<KeyCombination, Integer> mHardwareShortcuts;
+
+    public static final @NonNull Parcelable.Creator<KeyGlyphMap> CREATOR =
+            new Parcelable.Creator<>() {
+                public KeyGlyphMap createFromParcel(Parcel in) {
+                    return new KeyGlyphMap(in);
+                }
+
+                public KeyGlyphMap[] newArray(int size) {
+                    return new KeyGlyphMap[size];
+                }
+            };
+
+    public KeyGlyphMap(@NonNull ComponentName componentName,
+            @NonNull SparseIntArray keyGlyphs, @NonNull SparseIntArray modifierGlyphs,
+            @NonNull int[] functionRowKeys,
+            @NonNull Map<KeyCombination, Integer> hardwareShortcuts) {
+        mComponentName = componentName;
+        mKeyGlyphs = keyGlyphs;
+        mModifierGlyphs = modifierGlyphs;
+        mFunctionRowKeys = functionRowKeys;
+        mHardwareShortcuts = hardwareShortcuts;
+    }
+
+    public KeyGlyphMap(Parcel in) {
+        mComponentName = in.readParcelable(getClass().getClassLoader(), ComponentName.class);
+        mKeyGlyphs = in.readSparseIntArray();
+        mModifierGlyphs = in.readSparseIntArray();
+        mFunctionRowKeys = new int[in.readInt()];
+        in.readIntArray(mFunctionRowKeys);
+        mHardwareShortcuts = new HashMap<>(in.readInt());
+        in.readMap(mHardwareShortcuts, getClass().getClassLoader(), KeyCombination.class,
+                Integer.class);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mComponentName, 0);
+        dest.writeSparseIntArray(mKeyGlyphs);
+        dest.writeSparseIntArray(mModifierGlyphs);
+        dest.writeInt(mFunctionRowKeys.length);
+        dest.writeIntArray(mFunctionRowKeys);
+        dest.writeInt(mHardwareShortcuts.size());
+        dest.writeMap(mHardwareShortcuts);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Defines a key combination that includes a keycode and modifier state.
+     */
+    public record KeyCombination(int modifierState, int keycode) {}
+
+    /**
+     * Returns keycodes generated from the functional row defined for the keyboard.
+     */
+    public int[] getFunctionRowKeys() {
+        return mFunctionRowKeys;
+    }
+
+    /**
+     * Returns hardware defined shortcuts that are handled in the firmware of a particular
+     * keyboard (e.g. Fn+Backspace = Back, etc.)
+     *
+     * @return a map of (modifier + key) combinations to keycode mappings that are handled by the
+     * device hardware/firmware.
+     */
+    public Map<KeyCombination, Integer> getHardwareShortcuts() {
+        return mHardwareShortcuts;
+    }
+
+    /**
+     * Provides the drawable resource for the glyph for a keycode.
+     * Returns null if not available.
+     */
+    @Nullable
+    public Drawable getDrawableForKeycode(Context context, int keycode) {
+        return getDrawable(context, mKeyGlyphs.get(keycode, 0));
+    }
+
+    /**
+     * Provides the drawable resource for the glyph for a modifier key.
+     * Returns null if not available.
+     */
+    @Nullable
+    public Drawable getDrawableForModifier(Context context, int modifierKeycode) {
+        int modifier = switch (modifierKeycode) {
+            case KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT -> KeyEvent.META_META_ON;
+            case KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT -> KeyEvent.META_CTRL_ON;
+            case KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT -> KeyEvent.META_ALT_ON;
+            case KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT ->
+                    KeyEvent.META_SHIFT_ON;
+            case KeyEvent.KEYCODE_FUNCTION -> KeyEvent.META_FUNCTION_ON;
+            case KeyEvent.KEYCODE_SYM -> KeyEvent.META_SYM_ON;
+            case KeyEvent.KEYCODE_CAPS_LOCK -> KeyEvent.META_CAPS_LOCK_ON;
+            case KeyEvent.KEYCODE_NUM_LOCK -> KeyEvent.META_NUM_LOCK_ON;
+            case KeyEvent.KEYCODE_SCROLL_LOCK -> KeyEvent.META_SCROLL_LOCK_ON;
+            default -> 0;
+        };
+        return getDrawable(context, mModifierGlyphs.get(modifier, 0));
+    }
+
+    @Nullable
+    private Drawable getDrawable(Context context, @DrawableRes int drawableRes) {
+        PackageManager pm = context.getPackageManager();
+        try {
+            ActivityInfo receiver = pm.getReceiverInfo(mComponentName,
+                    PackageManager.GET_META_DATA
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            return resources.getDrawable(drawableRes, null);
+        } catch (PackageManager.NameNotFoundException ignored) {
+            Log.e(TAG, "Package name not found for " + mComponentName);
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "KeyGlyphMap{"
+                + "mComponentName=" + mComponentName
+                + ", mKeyGlyphs=" + mKeyGlyphs
+                + ", mModifierGlyphs=" + mModifierGlyphs
+                + ", mFunctionRowKeys=" + Arrays.toString(mFunctionRowKeys)
+                + ", mHardwareShortcuts=" + mHardwareShortcuts
+                + '}';
+    }
+}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ed536ce..acd0d00f 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -53,4 +53,11 @@
     name: "touchpad_tap_dragging"
     description: "Offers a setting to enable touchpad tap dragging"
     bug: "321978150"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "input_native"
+    name: "keyboard_glyph_map"
+    description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
+    bug: "345440920"
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7926afe..f30a9f5 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6367,6 +6367,33 @@
                 Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
     }
 
+    private static final String CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY =
+        PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "user_serial_number");
+
+    private final PropertyInvalidatedCache<Integer, Integer> mUserSerialNumberCache =
+            new PropertyInvalidatedCache<Integer, Integer>(
+                32, CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY) {
+                @Override
+                public Integer recompute(Integer query) {
+                    try {
+                        return mService.getUserSerialNumber(query);
+                    } catch (RemoteException re) {
+                        throw re.rethrowFromSystemServer();
+                    }
+                }
+                @Override
+                public boolean bypass(Integer query) {
+                    return query <= 0;
+                }
+            };
+
+
+    /** @hide */
+    public static final void invalidateUserSerialNumberCache() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY);
+    }
+
     /**
      * Returns a serial number on this device for a given userId. User handles can be recycled
      * when deleting and creating users, but serial numbers are not reused until the device is wiped.
@@ -6376,6 +6403,14 @@
      */
     @UnsupportedAppUsage
     public int getUserSerialNumber(@UserIdInt int userId) {
+        if (android.multiuser.Flags.cacheUserSerialNumber()) {
+            // System user serial number is always 0, and it always exists.
+            // There is no need to call binder for that.
+            if (userId == UserHandle.USER_SYSTEM) {
+               return UserHandle.USER_SERIAL_SYSTEM;
+            }
+            return mUserSerialNumberCache.query(userId);
+        }
         try {
             return mService.getUserSerialNumber(userId);
         } catch (RemoteException re) {
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index e029e52..5ef597d 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -213,3 +213,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "location_bypass_privacy_dashboard_enabled"
+    is_exported: true
+    namespace: "permissions"
+    description: "Show access entry of location bypass permission in the Privacy Dashboard"
+    bug: "325536053"
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2948129..6b0d301 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1405,7 +1405,8 @@
                         convertToComponentName(
                                 rawMetadata.getString(
                                         com.android.internal.R.styleable.Dream_settingsActivity),
-                                serviceInfo),
+                                serviceInfo,
+                                packageManager),
                         rawMetadata.getDrawable(
                                 com.android.internal.R.styleable.Dream_previewImage),
                         rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
@@ -1420,26 +1421,38 @@
     }
 
     @Nullable
-    private static ComponentName convertToComponentName(@Nullable String flattenedString,
-            ServiceInfo serviceInfo) {
+    private static ComponentName convertToComponentName(
+            @Nullable String flattenedString,
+            ServiceInfo serviceInfo,
+            PackageManager packageManager) {
         if (flattenedString == null) {
             return null;
         }
 
-        if (!flattenedString.contains("/")) {
-            return new ComponentName(serviceInfo.packageName, flattenedString);
+        final ComponentName cn =
+                flattenedString.contains("/")
+                        ? ComponentName.unflattenFromString(flattenedString)
+                        : new ComponentName(serviceInfo.packageName, flattenedString);
+
+        if (cn == null) {
+            return null;
         }
 
         // Ensure that the component is from the same package as the dream service. If not,
         // treat the component as invalid and return null instead.
-        final ComponentName cn = ComponentName.unflattenFromString(flattenedString);
-        if (cn == null) return null;
         if (!cn.getPackageName().equals(serviceInfo.packageName)) {
             Log.w(TAG,
                     "Inconsistent package name in component: " + cn.getPackageName()
                             + ", should be: " + serviceInfo.packageName);
             return null;
         }
+
+        // Ensure that the activity exists. If not, treat the component as invalid and return null.
+        if (new Intent().setComponent(cn).resolveActivityInfo(packageManager, 0) == null) {
+            Log.w(TAG, "Dream settings activity not found: " + cn);
+            return null;
+        }
+
         return cn;
     }
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 02ea6d4..df2af73 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -29,6 +29,7 @@
 import static android.view.WindowInsets.Type.ime;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.window.flags.Flags.insetsControlSeq;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -890,7 +891,9 @@
         @InsetsType int visibleTypes = 0;
         @InsetsType int[] cancelledUserAnimationTypes = {0};
         for (int i = 0, size = newState.sourceSize(); i < size; i++) {
-            final InsetsSource source = newState.sourceAt(i);
+            final InsetsSource source = insetsControlSeq()
+                    ? new InsetsSource(newState.sourceAt(i))
+                    : newState.sourceAt(i);
             @InsetsType int type = source.getType();
             @AnimationType int animationType = getAnimationType(type);
             final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId());
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 6a92fd9..c73cbc6 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -28,6 +28,7 @@
 import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.window.flags.Flags.insetsControlSeq;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -410,7 +411,9 @@
 
         // Frame is changing while animating. Keep note of the new frame but keep existing frame
         // until animation is finished.
-        newSource = new InsetsSource(newSource);
+        if (!insetsControlSeq()) {
+            newSource = new InsetsSource(newSource);
+        }
         mPendingFrame = new Rect(newSource.getFrame());
         mPendingVisibleFrame = newSource.getVisibleFrame() != null
                 ? new Rect(newSource.getVisibleFrame())
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 6420620..a3fcfad 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -72,6 +72,7 @@
             UserShortcutType.TRIPLETAP,
             UserShortcutType.TWOFINGER_DOUBLETAP,
             UserShortcutType.QUICK_SETTINGS,
+            UserShortcutType.GESTURE
     })
     public @interface UserShortcutType {
         int DEFAULT = 0;
@@ -81,6 +82,7 @@
         int TRIPLETAP = 1 << 2;
         int TWOFINGER_DOUBLETAP = 1 << 3;
         int QUICK_SETTINGS = 1 << 4;
+        int GESTURE = 1 << 5;
         // LINT.ThenChange(:shortcut_type_array)
     }
 
@@ -95,6 +97,7 @@
             UserShortcutType.TRIPLETAP,
             UserShortcutType.TWOFINGER_DOUBLETAP,
             UserShortcutType.QUICK_SETTINGS,
+            UserShortcutType.GESTURE
             // LINT.ThenChange(:shortcut_type_intdef)
     };
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index c8d6194..01cbb55 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityButtonLongPressStatus;
@@ -66,6 +67,9 @@
                 NAV_BAR_MODE_GESTURAL == getResources().getInteger(
                         com.android.internal.R.integer.config_navBarInteractionMode);
 
+        final int targetType = (isGestureNavigateEnabled
+                && android.provider.Flags.a11yStandaloneGestureEnabled()) ? GESTURE : SOFTWARE;
+
         if (isGestureNavigateEnabled) {
             final TextView promptPrologue = findViewById(R.id.accessibility_button_prompt_prologue);
             promptPrologue.setText(isTouchExploreOn
@@ -78,7 +82,7 @@
                     : R.string.accessibility_gesture_instructional_text);
         }
 
-        mTargets.addAll(getTargets(this, SOFTWARE));
+        mTargets.addAll(getTargets(this, targetType));
 
         final GridView gridview = findViewById(R.id.accessibility_button_chooser_grid);
         gridview.setAdapter(new ButtonTargetAdapter(mTargets));
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 6256dbc..44c7543 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -18,7 +18,6 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
@@ -213,10 +212,7 @@
         final boolean isEditMenuMode =
                 mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
         final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
-        final int editDialogTitleId =
-                mShortcutType == SOFTWARE
-                        ? R.string.accessibility_edit_shortcut_menu_button_title
-                        : R.string.accessibility_edit_shortcut_menu_volume_title;
+        final int editDialogTitleId = R.string.accessibility_edit_shortcut_menu_volume_title;
 
         mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
         mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ec90dd2..a753110 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.accessibility.dialog;
 
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
@@ -197,6 +198,9 @@
     @VisibleForTesting
     public static boolean isRecognizedShortcutType(@UserShortcutType int shortcutType) {
         int mask = SOFTWARE | HARDWARE;
+        if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            mask = mask | GESTURE;
+        }
         return (shortcutType != 0 && (shortcutType & mask) == shortcutType);
     }
 }
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 8e18f84..9d66461 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -23,6 +23,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
@@ -247,6 +248,8 @@
                 } else {
                     return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
                 }
+            case GESTURE:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
             case HARDWARE:
                 return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
             case QUICK_SETTINGS:
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 6b0ca9f..48f86ff 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -166,6 +166,8 @@
         switch (type) {
             case UserShortcutType.SOFTWARE:
                 return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
+            case UserShortcutType.GESTURE:
+                return Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
             case UserShortcutType.HARDWARE:
                 return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
             case UserShortcutType.TRIPLETAP:
@@ -190,6 +192,7 @@
     public static int convertToType(String key) {
         return switch (key) {
             case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> UserShortcutType.SOFTWARE;
+            case Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS -> UserShortcutType.GESTURE;
             case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> UserShortcutType.QUICK_SETTINGS;
             case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> UserShortcutType.HARDWARE;
             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED ->
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 13014bf..0118c05 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -39,7 +39,9 @@
  * Methods in this class are stubs, that are replaced by optimised versions by the ProtoLogTool
  * during build.
  */
+// LINT.IfChange
 public class ProtoLog {
+// LINT.ThenChange(frameworks/base/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt)
 
     // Needs to be set directly otherwise the protologtool tries to transform the method call
     public static boolean REQUIRE_PROTOLOGTOOL = true;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4883827..ea993ee 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2184,6 +2184,344 @@
         <enum name="KEYCODE_DEMO_APP_2" value="302" />
         <enum name="KEYCODE_DEMO_APP_3" value="303" />
         <enum name="KEYCODE_DEMO_APP_4" value="304" />
+        <enum name="KEYCODE_KEYBOARD_BACKLIGHT_DOWN" value="305" />
+        <enum name="KEYCODE_KEYBOARD_BACKLIGHT_UP" value="306" />
+        <enum name="KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE" value="307" />
+        <enum name="KEYCODE_STYLUS_BUTTON_PRIMARY" value="308" />
+        <enum name="KEYCODE_STYLUS_BUTTON_SECONDARY" value="309" />
+        <enum name="KEYCODE_STYLUS_BUTTON_TERTIARY" value="310" />
+        <enum name="KEYCODE_STYLUS_BUTTON_TAIL" value="311" />
+        <enum name="KEYCODE_RECENT_APPS" value="312" />
+        <enum name="KEYCODE_MACRO_1" value="313" />
+        <enum name="KEYCODE_MACRO_2" value="314" />
+        <enum name="KEYCODE_MACRO_3" value="315" />
+        <enum name="KEYCODE_MACRO_4" value="316" />
+        <enum name="KEYCODE_EMOJI_PICKER" value="317" />
+        <enum name="KEYCODE_SCREENSHOT" value="318" />
+    </attr>
+
+    <!-- @hide same as keycode enum defined above, but to be used to define keycode output.
+         (redefining it to allow keycode and outKeycode to be part of same styleable attribute) -->
+    <attr name="outKeycode">
+        <enum name="KEYCODE_UNKNOWN" value="0" />
+        <enum name="KEYCODE_SOFT_LEFT" value="1" />
+        <enum name="KEYCODE_SOFT_RIGHT" value="2" />
+        <enum name="KEYCODE_HOME" value="3" />
+        <enum name="KEYCODE_BACK" value="4" />
+        <enum name="KEYCODE_CALL" value="5" />
+        <enum name="KEYCODE_ENDCALL" value="6" />
+        <enum name="KEYCODE_0" value="7" />
+        <enum name="KEYCODE_1" value="8" />
+        <enum name="KEYCODE_2" value="9" />
+        <enum name="KEYCODE_3" value="10" />
+        <enum name="KEYCODE_4" value="11" />
+        <enum name="KEYCODE_5" value="12" />
+        <enum name="KEYCODE_6" value="13" />
+        <enum name="KEYCODE_7" value="14" />
+        <enum name="KEYCODE_8" value="15" />
+        <enum name="KEYCODE_9" value="16" />
+        <enum name="KEYCODE_STAR" value="17" />
+        <enum name="KEYCODE_POUND" value="18" />
+        <enum name="KEYCODE_DPAD_UP" value="19" />
+        <enum name="KEYCODE_DPAD_DOWN" value="20" />
+        <enum name="KEYCODE_DPAD_LEFT" value="21" />
+        <enum name="KEYCODE_DPAD_RIGHT" value="22" />
+        <enum name="KEYCODE_DPAD_CENTER" value="23" />
+        <enum name="KEYCODE_VOLUME_UP" value="24" />
+        <enum name="KEYCODE_VOLUME_DOWN" value="25" />
+        <enum name="KEYCODE_POWER" value="26" />
+        <enum name="KEYCODE_CAMERA" value="27" />
+        <enum name="KEYCODE_CLEAR" value="28" />
+        <enum name="KEYCODE_A" value="29" />
+        <enum name="KEYCODE_B" value="30" />
+        <enum name="KEYCODE_C" value="31" />
+        <enum name="KEYCODE_D" value="32" />
+        <enum name="KEYCODE_E" value="33" />
+        <enum name="KEYCODE_F" value="34" />
+        <enum name="KEYCODE_G" value="35" />
+        <enum name="KEYCODE_H" value="36" />
+        <enum name="KEYCODE_I" value="37" />
+        <enum name="KEYCODE_J" value="38" />
+        <enum name="KEYCODE_K" value="39" />
+        <enum name="KEYCODE_L" value="40" />
+        <enum name="KEYCODE_M" value="41" />
+        <enum name="KEYCODE_N" value="42" />
+        <enum name="KEYCODE_O" value="43" />
+        <enum name="KEYCODE_P" value="44" />
+        <enum name="KEYCODE_Q" value="45" />
+        <enum name="KEYCODE_R" value="46" />
+        <enum name="KEYCODE_S" value="47" />
+        <enum name="KEYCODE_T" value="48" />
+        <enum name="KEYCODE_U" value="49" />
+        <enum name="KEYCODE_V" value="50" />
+        <enum name="KEYCODE_W" value="51" />
+        <enum name="KEYCODE_X" value="52" />
+        <enum name="KEYCODE_Y" value="53" />
+        <enum name="KEYCODE_Z" value="54" />
+        <enum name="KEYCODE_COMMA" value="55" />
+        <enum name="KEYCODE_PERIOD" value="56" />
+        <enum name="KEYCODE_ALT_LEFT" value="57" />
+        <enum name="KEYCODE_ALT_RIGHT" value="58" />
+        <enum name="KEYCODE_SHIFT_LEFT" value="59" />
+        <enum name="KEYCODE_SHIFT_RIGHT" value="60" />
+        <enum name="KEYCODE_TAB" value="61" />
+        <enum name="KEYCODE_SPACE" value="62" />
+        <enum name="KEYCODE_SYM" value="63" />
+        <enum name="KEYCODE_EXPLORER" value="64" />
+        <enum name="KEYCODE_ENVELOPE" value="65" />
+        <enum name="KEYCODE_ENTER" value="66" />
+        <enum name="KEYCODE_DEL" value="67" />
+        <enum name="KEYCODE_GRAVE" value="68" />
+        <enum name="KEYCODE_MINUS" value="69" />
+        <enum name="KEYCODE_EQUALS" value="70" />
+        <enum name="KEYCODE_LEFT_BRACKET" value="71" />
+        <enum name="KEYCODE_RIGHT_BRACKET" value="72" />
+        <enum name="KEYCODE_BACKSLASH" value="73" />
+        <enum name="KEYCODE_SEMICOLON" value="74" />
+        <enum name="KEYCODE_APOSTROPHE" value="75" />
+        <enum name="KEYCODE_SLASH" value="76" />
+        <enum name="KEYCODE_AT" value="77" />
+        <enum name="KEYCODE_NUM" value="78" />
+        <enum name="KEYCODE_HEADSETHOOK" value="79" />
+        <enum name="KEYCODE_FOCUS" value="80" />
+        <enum name="KEYCODE_PLUS" value="81" />
+        <enum name="KEYCODE_MENU" value="82" />
+        <enum name="KEYCODE_NOTIFICATION" value="83" />
+        <enum name="KEYCODE_SEARCH" value="84" />
+        <enum name="KEYCODE_MEDIA_PLAY_PAUSE" value="85" />
+        <enum name="KEYCODE_MEDIA_STOP" value="86" />
+        <enum name="KEYCODE_MEDIA_NEXT" value="87" />
+        <enum name="KEYCODE_MEDIA_PREVIOUS" value="88" />
+        <enum name="KEYCODE_MEDIA_REWIND" value="89" />
+        <enum name="KEYCODE_MEDIA_FAST_FORWARD" value="90" />
+        <enum name="KEYCODE_MUTE" value="91" />
+        <enum name="KEYCODE_PAGE_UP" value="92" />
+        <enum name="KEYCODE_PAGE_DOWN" value="93" />
+        <enum name="KEYCODE_PICTSYMBOLS" value="94" />
+        <enum name="KEYCODE_SWITCH_CHARSET" value="95" />
+        <enum name="KEYCODE_BUTTON_A" value="96" />
+        <enum name="KEYCODE_BUTTON_B" value="97" />
+        <enum name="KEYCODE_BUTTON_C" value="98" />
+        <enum name="KEYCODE_BUTTON_X" value="99" />
+        <enum name="KEYCODE_BUTTON_Y" value="100" />
+        <enum name="KEYCODE_BUTTON_Z" value="101" />
+        <enum name="KEYCODE_BUTTON_L1" value="102" />
+        <enum name="KEYCODE_BUTTON_R1" value="103" />
+        <enum name="KEYCODE_BUTTON_L2" value="104" />
+        <enum name="KEYCODE_BUTTON_R2" value="105" />
+        <enum name="KEYCODE_BUTTON_THUMBL" value="106" />
+        <enum name="KEYCODE_BUTTON_THUMBR" value="107" />
+        <enum name="KEYCODE_BUTTON_START" value="108" />
+        <enum name="KEYCODE_BUTTON_SELECT" value="109" />
+        <enum name="KEYCODE_BUTTON_MODE" value="110" />
+        <enum name="KEYCODE_ESCAPE" value="111" />
+        <enum name="KEYCODE_FORWARD_DEL" value="112" />
+        <enum name="KEYCODE_CTRL_LEFT" value="113" />
+        <enum name="KEYCODE_CTRL_RIGHT" value="114" />
+        <enum name="KEYCODE_CAPS_LOCK" value="115" />
+        <enum name="KEYCODE_SCROLL_LOCK" value="116" />
+        <enum name="KEYCODE_META_LEFT" value="117" />
+        <enum name="KEYCODE_META_RIGHT" value="118" />
+        <enum name="KEYCODE_FUNCTION" value="119" />
+        <enum name="KEYCODE_SYSRQ" value="120" />
+        <enum name="KEYCODE_BREAK" value="121" />
+        <enum name="KEYCODE_MOVE_HOME" value="122" />
+        <enum name="KEYCODE_MOVE_END" value="123" />
+        <enum name="KEYCODE_INSERT" value="124" />
+        <enum name="KEYCODE_FORWARD" value="125" />
+        <enum name="KEYCODE_MEDIA_PLAY" value="126" />
+        <enum name="KEYCODE_MEDIA_PAUSE" value="127" />
+        <enum name="KEYCODE_MEDIA_CLOSE" value="128" />
+        <enum name="KEYCODE_MEDIA_EJECT" value="129" />
+        <enum name="KEYCODE_MEDIA_RECORD" value="130" />
+        <enum name="KEYCODE_F1" value="131" />
+        <enum name="KEYCODE_F2" value="132" />
+        <enum name="KEYCODE_F3" value="133" />
+        <enum name="KEYCODE_F4" value="134" />
+        <enum name="KEYCODE_F5" value="135" />
+        <enum name="KEYCODE_F6" value="136" />
+        <enum name="KEYCODE_F7" value="137" />
+        <enum name="KEYCODE_F8" value="138" />
+        <enum name="KEYCODE_F9" value="139" />
+        <enum name="KEYCODE_F10" value="140" />
+        <enum name="KEYCODE_F11" value="141" />
+        <enum name="KEYCODE_F12" value="142" />
+        <enum name="KEYCODE_NUM_LOCK" value="143" />
+        <enum name="KEYCODE_NUMPAD_0" value="144" />
+        <enum name="KEYCODE_NUMPAD_1" value="145" />
+        <enum name="KEYCODE_NUMPAD_2" value="146" />
+        <enum name="KEYCODE_NUMPAD_3" value="147" />
+        <enum name="KEYCODE_NUMPAD_4" value="148" />
+        <enum name="KEYCODE_NUMPAD_5" value="149" />
+        <enum name="KEYCODE_NUMPAD_6" value="150" />
+        <enum name="KEYCODE_NUMPAD_7" value="151" />
+        <enum name="KEYCODE_NUMPAD_8" value="152" />
+        <enum name="KEYCODE_NUMPAD_9" value="153" />
+        <enum name="KEYCODE_NUMPAD_DIVIDE" value="154" />
+        <enum name="KEYCODE_NUMPAD_MULTIPLY" value="155" />
+        <enum name="KEYCODE_NUMPAD_SUBTRACT" value="156" />
+        <enum name="KEYCODE_NUMPAD_ADD" value="157" />
+        <enum name="KEYCODE_NUMPAD_DOT" value="158" />
+        <enum name="KEYCODE_NUMPAD_COMMA" value="159" />
+        <enum name="KEYCODE_NUMPAD_ENTER" value="160" />
+        <enum name="KEYCODE_NUMPAD_EQUALS" value="161" />
+        <enum name="KEYCODE_NUMPAD_LEFT_PAREN" value="162" />
+        <enum name="KEYCODE_NUMPAD_RIGHT_PAREN" value="163" />
+        <enum name="KEYCODE_VOLUME_MUTE" value="164" />
+        <enum name="KEYCODE_INFO" value="165" />
+        <enum name="KEYCODE_CHANNEL_UP" value="166" />
+        <enum name="KEYCODE_CHANNEL_DOWN" value="167" />
+        <enum name="KEYCODE_ZOOM_IN" value="168" />
+        <enum name="KEYCODE_ZOOM_OUT" value="169" />
+        <enum name="KEYCODE_TV" value="170" />
+        <enum name="KEYCODE_WINDOW" value="171" />
+        <enum name="KEYCODE_GUIDE" value="172" />
+        <enum name="KEYCODE_DVR" value="173" />
+        <enum name="KEYCODE_BOOKMARK" value="174" />
+        <enum name="KEYCODE_CAPTIONS" value="175" />
+        <enum name="KEYCODE_SETTINGS" value="176" />
+        <enum name="KEYCODE_TV_POWER" value="177" />
+        <enum name="KEYCODE_TV_INPUT" value="178" />
+        <enum name="KEYCODE_STB_POWER" value="179" />
+        <enum name="KEYCODE_STB_INPUT" value="180" />
+        <enum name="KEYCODE_AVR_POWER" value="181" />
+        <enum name="KEYCODE_AVR_INPUT" value="182" />
+        <enum name="KEYCODE_PROG_GRED" value="183" />
+        <enum name="KEYCODE_PROG_GREEN" value="184" />
+        <enum name="KEYCODE_PROG_YELLOW" value="185" />
+        <enum name="KEYCODE_PROG_BLUE" value="186" />
+        <enum name="KEYCODE_APP_SWITCH" value="187" />
+        <enum name="KEYCODE_BUTTON_1" value="188" />
+        <enum name="KEYCODE_BUTTON_2" value="189" />
+        <enum name="KEYCODE_BUTTON_3" value="190" />
+        <enum name="KEYCODE_BUTTON_4" value="191" />
+        <enum name="KEYCODE_BUTTON_5" value="192" />
+        <enum name="KEYCODE_BUTTON_6" value="193" />
+        <enum name="KEYCODE_BUTTON_7" value="194" />
+        <enum name="KEYCODE_BUTTON_8" value="195" />
+        <enum name="KEYCODE_BUTTON_9" value="196" />
+        <enum name="KEYCODE_BUTTON_10" value="197" />
+        <enum name="KEYCODE_BUTTON_11" value="198" />
+        <enum name="KEYCODE_BUTTON_12" value="199" />
+        <enum name="KEYCODE_BUTTON_13" value="200" />
+        <enum name="KEYCODE_BUTTON_14" value="201" />
+        <enum name="KEYCODE_BUTTON_15" value="202" />
+        <enum name="KEYCODE_BUTTON_16" value="203" />
+        <enum name="KEYCODE_LANGUAGE_SWITCH" value="204" />
+        <enum name="KEYCODE_MANNER_MODE" value="205" />
+        <enum name="KEYCODE_3D_MODE" value="206" />
+        <enum name="KEYCODE_CONTACTS" value="207" />
+        <enum name="KEYCODE_CALENDAR" value="208" />
+        <enum name="KEYCODE_MUSIC" value="209" />
+        <enum name="KEYCODE_CALCULATOR" value="210" />
+        <enum name="KEYCODE_ZENKAKU_HANKAKU" value="211" />
+        <enum name="KEYCODE_EISU" value="212" />
+        <enum name="KEYCODE_MUHENKAN" value="213" />
+        <enum name="KEYCODE_HENKAN" value="214" />
+        <enum name="KEYCODE_KATAKANA_HIRAGANA" value="215" />
+        <enum name="KEYCODE_YEN" value="216" />
+        <enum name="KEYCODE_RO" value="217" />
+        <enum name="KEYCODE_KANA" value="218" />
+        <enum name="KEYCODE_ASSIST" value="219" />
+        <enum name="KEYCODE_BRIGHTNESS_DOWN" value="220" />
+        <enum name="KEYCODE_BRIGHTNESS_UP" value="221" />
+        <enum name="KEYCODE_MEDIA_AUDIO_TRACK" value="222" />
+        <enum name="KEYCODE_MEDIA_SLEEP" value="223" />
+        <enum name="KEYCODE_MEDIA_WAKEUP" value="224" />
+        <enum name="KEYCODE_PAIRING" value="225" />
+        <enum name="KEYCODE_MEDIA_TOP_MENU" value="226" />
+        <enum name="KEYCODE_11" value="227" />
+        <enum name="KEYCODE_12" value="228" />
+        <enum name="KEYCODE_LAST_CHANNEL" value="229" />
+        <enum name="KEYCODE_TV_DATA_SERVICE" value="230" />
+        <enum name="KEYCODE_VOICE_ASSIST" value="231" />
+        <enum name="KEYCODE_TV_RADIO_SERVICE" value="232" />
+        <enum name="KEYCODE_TV_TELETEXT" value="233" />
+        <enum name="KEYCODE_TV_NUMBER_ENTRY" value="234" />
+        <enum name="KEYCODE_TV_TERRESTRIAL_ANALOG" value="235" />
+        <enum name="KEYCODE_TV_TERRESTRIAL_DIGITAL" value="236" />
+        <enum name="KEYCODE_TV_SATELLITE" value="237" />
+        <enum name="KEYCODE_TV_SATELLITE_BS" value="238" />
+        <enum name="KEYCODE_TV_SATELLITE_CS" value="239" />
+        <enum name="KEYCODE_TV_SATELLITE_SERVICE" value="240" />
+        <enum name="KEYCODE_TV_NETWORK" value="241" />
+        <enum name="KEYCODE_TV_ANTENNA_CABLE" value="242" />
+        <enum name="KEYCODE_TV_INPUT_HDMI_1" value="243" />
+        <enum name="KEYCODE_TV_INPUT_HDMI_2" value="244" />
+        <enum name="KEYCODE_TV_INPUT_HDMI_3" value="245" />
+        <enum name="KEYCODE_TV_INPUT_HDMI_4" value="246" />
+        <enum name="KEYCODE_TV_INPUT_COMPOSITE_1" value="247" />
+        <enum name="KEYCODE_TV_INPUT_COMPOSITE_2" value="248" />
+        <enum name="KEYCODE_TV_INPUT_COMPONENT_1" value="249" />
+        <enum name="KEYCODE_TV_INPUT_COMPONENT_2" value="250" />
+        <enum name="KEYCODE_TV_INPUT_VGA_1" value="251" />
+        <enum name="KEYCODE_TV_AUDIO_DESCRIPTION" value="252" />
+        <enum name="KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP" value="253" />
+        <enum name="KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN" value="254" />
+        <enum name="KEYCODE_TV_ZOOM_MODE" value="255" />
+        <enum name="KEYCODE_TV_CONTENTS_MENU" value="256" />
+        <enum name="KEYCODE_TV_MEDIA_CONTEXT_MENU" value="257" />
+        <enum name="KEYCODE_TV_TIMER_PROGRAMMING" value="258" />
+        <enum name="KEYCODE_HELP" value="259" />
+        <enum name="KEYCODE_NAVIGATE_PREVIOUS" value="260" />
+        <enum name="KEYCODE_NAVIGATE_NEXT" value="261" />
+        <enum name="KEYCODE_NAVIGATE_IN" value="262" />
+        <enum name="KEYCODE_NAVIGATE_OUT" value="263" />
+        <enum name="KEYCODE_STEM_PRIMARY" value="264" />
+        <enum name="KEYCODE_STEM_1" value="265" />
+        <enum name="KEYCODE_STEM_2" value="266" />
+        <enum name="KEYCODE_STEM_3" value="267" />
+        <enum name="KEYCODE_DPAD_UP_LEFT" value="268" />
+        <enum name="KEYCODE_DPAD_DOWN_LEFT" value="269" />
+        <enum name="KEYCODE_DPAD_UP_RIGHT" value="270" />
+        <enum name="KEYCODE_DPAD_DOWN_RIGHT" value="271" />
+        <enum name="KEYCODE_MEDIA_SKIP_FORWARD" value="272" />
+        <enum name="KEYCODE_MEDIA_SKIP_BACKWARD" value="273" />
+        <enum name="KEYCODE_MEDIA_STEP_FORWARD" value="274" />
+        <enum name="KEYCODE_MEDIA_STEP_BACKWARD" value="275" />
+        <enum name="KEYCODE_SOFT_SLEEP" value="276" />
+        <enum name="KEYCODE_CUT" value="277" />
+        <enum name="KEYCODE_COPY" value="278" />
+        <enum name="KEYCODE_PASTE" value="279" />
+        <enum name="KEYCODE_SYSTEM_NAVIGATION_UP" value="280" />
+        <enum name="KEYCODE_SYSTEM_NAVIGATION_DOWN" value="281" />
+        <enum name="KEYCODE_SYSTEM_NAVIGATION_LEFT" value="282" />
+        <enum name="KEYCODE_SYSTEM_NAVIGATION_RIGHT" value="283" />
+        <enum name="KEYCODE_ALL_APPS" value="284" />
+        <enum name="KEYCODE_REFRESH" value="285" />
+        <enum name="KEYCODE_THUMBS_UP" value="286" />
+        <enum name="KEYCODE_THUMBS_DOWN" value="287" />
+        <enum name="KEYCODE_PROFILE_SWITCH" value="288" />
+        <enum name="KEYCODE_VIDEO_APP_1" value="289" />
+        <enum name="KEYCODE_VIDEO_APP_2" value="290" />
+        <enum name="KEYCODE_VIDEO_APP_3" value="291" />
+        <enum name="KEYCODE_VIDEO_APP_4" value="292" />
+        <enum name="KEYCODE_VIDEO_APP_5" value="293" />
+        <enum name="KEYCODE_VIDEO_APP_6" value="294" />
+        <enum name="KEYCODE_VIDEO_APP_7" value="295" />
+        <enum name="KEYCODE_VIDEO_APP_8" value="296" />
+        <enum name="KEYCODE_FEATURED_APP_1" value="297" />
+        <enum name="KEYCODE_FEATURED_APP_2" value="298" />
+        <enum name="KEYCODE_FEATURED_APP_3" value="299" />
+        <enum name="KEYCODE_FEATURED_APP_4" value="300" />
+        <enum name="KEYCODE_DEMO_APP_1" value="301" />
+        <enum name="KEYCODE_DEMO_APP_2" value="302" />
+        <enum name="KEYCODE_DEMO_APP_3" value="303" />
+        <enum name="KEYCODE_DEMO_APP_4" value="304" />
+        <enum name="KEYCODE_KEYBOARD_BACKLIGHT_DOWN" value="305" />
+        <enum name="KEYCODE_KEYBOARD_BACKLIGHT_UP" value="306" />
+        <enum name="KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE" value="307" />
+        <enum name="KEYCODE_STYLUS_BUTTON_PRIMARY" value="308" />
+        <enum name="KEYCODE_STYLUS_BUTTON_SECONDARY" value="309" />
+        <enum name="KEYCODE_STYLUS_BUTTON_TERTIARY" value="310" />
+        <enum name="KEYCODE_STYLUS_BUTTON_TAIL" value="311" />
+        <enum name="KEYCODE_RECENT_APPS" value="312" />
+        <enum name="KEYCODE_MACRO_1" value="313" />
+        <enum name="KEYCODE_MACRO_2" value="314" />
+        <enum name="KEYCODE_MACRO_3" value="315" />
+        <enum name="KEYCODE_MACRO_4" value="316" />
+        <enum name="KEYCODE_EMOJI_PICKER" value="317" />
+        <enum name="KEYCODE_SCREENSHOT" value="318" />
     </attr>
 
     <!-- ***************************************************************** -->
@@ -9844,6 +10182,63 @@
         <attr name="productId" format="integer" />
     </declare-styleable>
 
+    <!-- @hide -->
+    <declare-styleable name="KeyboardGlyphMap">
+        <attr name="glyphMap" format="reference" />
+        <attr name="vendorId" format="integer" />
+        <attr name="productId" format="integer" />
+    </declare-styleable>
+
+    <!-- @hide -->
+    <declare-styleable name="KeyGlyph">
+        <attr name="keycode" />
+        <attr name="glyphDrawable" format="reference" />
+    </declare-styleable>
+
+    <!-- @hide -->
+    <declare-styleable name="ModifierGlyph">
+        <!-- The values are taken from public constants for modifier state defined in
+             {@see KeyEvent.java}. Here we explicitly allow only one modifier bit as value, since
+             this represents the modifier key -->
+        <attr name="modifier">
+            <enum name="META" value="0x10000" />
+            <enum name="CTRL" value="0x1000" />
+            <enum name="ALT" value="0x02" />
+            <enum name="SHIFT" value="0x1" />
+            <enum name="SYM" value="0x4" />
+            <enum name="FUNCTION" value="0x8" />
+            <enum name="CAPS_LOCK" value="0x100000" />
+            <enum name="NUM_LOCK" value="0x200000" />
+            <enum name="SCROLL_LOCK" value="0x400000" />
+        </attr>
+        <attr name="glyphDrawable" format="reference" />
+    </declare-styleable>
+
+    <!-- @hide -->
+    <declare-styleable name="HardwareDefinedShortcut">
+        <attr name="keycode" />
+        <!-- The values are taken from public constants for modifier state defined in
+             {@see KeyEvent.java}. Here we allow multiple modifier flags as value, since this
+             represents the modifier state -->
+        <attr name="modifierState">
+            <flag name="META" value="0x10000" />
+            <flag name="CTRL" value="0x1000" />
+            <flag name="ALT" value="0x02" />
+            <flag name="SHIFT" value="0x1" />
+            <flag name="SYM" value="0x4" />
+            <flag name="FUNCTION" value="0x8" />
+            <flag name="CAPS_LOCK" value="0x100000" />
+            <flag name="NUM_LOCK" value="0x200000" />
+            <flag name="SCROLL_LOCK" value="0x400000" />
+        </attr>
+        <attr name="outKeycode" />
+    </declare-styleable>
+
+    <!-- @hide -->
+    <declare-styleable name="FunctionRowKey">
+        <attr name="keycode" />
+    </declare-styleable>
+
     <declare-styleable name="MediaRouteButton">
         <!-- This drawable is a state list where the "activated" state
              indicates active media routing. Non-activated indicates
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e983427..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -424,4 +424,11 @@
     <bool name="config_dropboxmanager_persistent_logging_enabled">false</bool>
     <java-symbol type="bool" name="config_dropboxmanager_persistent_logging_enabled" />
 
+    <!-- Telephony satellite gateway class name for handling carrier roaming to satellite is using ESOS messaging. -->
+    <string name="config_satellite_carrier_roaming_esos_provisioned_class" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_class" />
+
+    <!-- Telephony satellite gateway intent for handling carrier roaming to satellite is using ESOS messaging. -->
+    <string name="config_satellite_carrier_roaming_esos_provisioned_intent_action" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_intent_action" />
 </resources>
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
index 389b677..f01ac6f 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
@@ -16,15 +16,22 @@
 
 package com.android.internal.accessibility.dialog;
 
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Flags;
+
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,9 +39,13 @@
 
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityTargetTest {
+    @Rule
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final int[] EXPECTED_TYPES = { HARDWARE, SOFTWARE };
+    private static final int[] EXPECTED_TYPES_GESTURE = { HARDWARE, SOFTWARE, GESTURE };
 
     @Test
+    @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void isRecognizedShortcutType_expectedType_isTrue() {
         for (int type : EXPECTED_TYPES) {
             assertThat(AccessibilityTarget.isRecognizedShortcutType(type)).isTrue();
@@ -42,6 +53,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void isRecognizedShortcutType_notExpectedType_isFalse() {
         for (int type: ShortcutConstants.USER_SHORTCUT_TYPES) {
             if (IntStream.of(EXPECTED_TYPES).noneMatch(x -> x == type)) {
@@ -49,4 +61,28 @@
             }
         }
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void isRecognizedShortcutType_expectedType_gestureIncluded_isTrue() {
+        for (int type : EXPECTED_TYPES_GESTURE) {
+            if (!AccessibilityTarget.isRecognizedShortcutType(type)) {
+                throw new AssertionError(
+                        "Shortcut type " + type + " should be recognized");
+            }
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void isRecognizedShortcutType_notExpectedType_gestureIncluded_isFalse() {
+        for (int type: ShortcutConstants.USER_SHORTCUT_TYPES) {
+            if (IntStream.of(EXPECTED_TYPES_GESTURE).noneMatch(x -> x == type)) {
+                if (AccessibilityTarget.isRecognizedShortcutType(type)) {
+                    throw new AssertionError(
+                            "Shortcut type " + type + " should not be recognized");
+                }
+            }
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
index 928fce9..8bebc62 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
@@ -20,6 +20,8 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -99,8 +101,7 @@
     public void getShortcutTargets_softwareShortcutNoService_emptyResult() {
         assertThat(
                 ShortcutUtils.getShortcutTargetsFromSettings(
-                        mContext,
-                        ShortcutConstants.UserShortcutType.SOFTWARE, mDefaultUserId)
+                        mContext, SOFTWARE, mDefaultUserId)
         ).isEmpty();
     }
 
@@ -114,13 +115,21 @@
     }
 
     @Test
+    public void getShortcutTargets_gestureShortcutNoService_emptyResult() {
+        assertThat(
+                ShortcutUtils.getShortcutTargetsFromSettings(
+                        mContext, GESTURE, mDefaultUserId)
+        ).isEmpty();
+    }
+
+    @Test
     public void getShortcutTargets_softwareShortcut1Service_return1Service() {
         setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
         setupShortcutTargets(TWO_COMPONENTS, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
 
         assertThat(
                 ShortcutUtils.getShortcutTargetsFromSettings(
-                        mContext, ShortcutConstants.UserShortcutType.SOFTWARE,
+                        mContext, SOFTWARE,
                         mDefaultUserId)
         ).containsExactlyElementsIn(ONE_COMPONENT);
     }
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 66b47da..a115c65 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -40,6 +40,7 @@
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
+        <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index f9a6caf..612b387 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,7 @@
 package androidx.window.extensions.embedding;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
@@ -29,6 +30,7 @@
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
 
+import android.annotation.ColorInt;
 import android.app.Activity;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Intent;
@@ -391,13 +393,22 @@
         if (splitAttributes == null) {
             return TaskFragmentAnimationParams.DEFAULT;
         }
+        final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
+        TaskFragmentAnimationParams.Builder builder = new TaskFragmentAnimationParams.Builder();
+        if (animationBackgroundColor != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
+            builder.setAnimationBackgroundColor(animationBackgroundColor);
+        }
+        // TODO(b/293658614): Allow setting custom open/close/changeAnimationResId.
+        return builder.build();
+    }
+
+    @ColorInt
+    private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
+        int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
         final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
         if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
-            return new TaskFragmentAnimationParams.Builder()
-                    .setAnimationBackgroundColor(colorBackground.getColor())
-                    .build();
-        } else {
-            return TaskFragmentAnimationParams.DEFAULT;
+            animationBackgroundColor = colorBackground.getColor();
         }
+        return animationBackgroundColor;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 217b1d3..1bf1259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -19,6 +19,8 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
 import android.content.pm.ActivityInfo.isFixedOrientationLandscape
 import android.content.pm.ActivityInfo.isFixedOrientationPortrait
 import android.content.res.Configuration.ORIENTATION_LANDSCAPE
@@ -105,7 +107,7 @@
  * Calculates the largest size that can fit in a given area while maintaining a specific aspect
  * ratio.
  */
-private fun maximumSizeMaintainingAspectRatio(
+fun maximumSizeMaintainingAspectRatio(
     taskInfo: RunningTaskInfo,
     targetArea: Size,
     aspectRatio: Float
@@ -114,7 +116,8 @@
     val targetWidth = targetArea.width
     val finalHeight: Int
     val finalWidth: Int
-    if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+    // Get orientation either through top activity or task's orientation
+    if (taskInfo.hasPortraitTopActivity()) {
         val tempWidth = (targetHeight / aspectRatio).toInt()
         if (tempWidth <= targetWidth) {
             finalHeight = targetHeight
@@ -137,7 +140,7 @@
 }
 
 /** Calculates the aspect ratio of an activity from its fullscreen bounds. */
-private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
     if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
         val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
         val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
@@ -171,3 +174,41 @@
         desiredSize.height + heightOffset
     )
 }
+
+/**
+ * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
+ * entire screen, as area can be offset by left and top start.
+ */
+fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect {
+    val heightOffset = (areaBounds.height() - desiredSize.height) / 2
+    val widthOffset = (areaBounds.width() - desiredSize.width) / 2
+
+    val newLeft = leftStart + widthOffset
+    val newTop = topStart + heightOffset
+    val newRight = newLeft + desiredSize.width
+    val newBottom = newTop + desiredSize.height
+
+    return Rect(newLeft, newTop, newRight, newBottom)
+}
+
+fun TaskInfo.hasPortraitTopActivity(): Boolean {
+    val topActivityScreenOrientation =
+        topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
+    val appBounds = configuration.windowConfiguration.appBounds
+
+    return when {
+        // First check if activity has portrait screen orientation
+        topActivityScreenOrientation != SCREEN_ORIENTATION_UNSPECIFIED -> {
+            isFixedOrientationPortrait(topActivityScreenOrientation)
+        }
+
+        // Then check if the activity is portrait when letterboxed
+        appCompatTaskInfo.topActivityBoundsLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+
+        // Then check if the activity is portrait
+        appBounds != null -> appBounds.height() > appBounds.width()
+
+        // Otherwise just take the orientation of the task
+        else -> isFixedOrientationPortrait(configuration.orientation)
+    }
+}
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 5813f85..7cc01d5 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
@@ -36,6 +36,7 @@
 import android.graphics.Region
 import android.os.IBinder
 import android.os.SystemProperties
+import android.util.Size
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
@@ -416,7 +417,7 @@
         )
         val wct = WindowContainerTransaction()
         exitSplitIfApplicable(wct, taskInfo)
-        moveHomeTaskToFront(wct)
+        moveHomeTask(wct, true /* toTop */)
         val taskToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
         addMoveToDesktopChanges(wct, taskInfo)
@@ -649,13 +650,21 @@
     fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
 
-        val stableBounds = Rect()
-        displayLayout.getStableBounds(stableBounds)
+        val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
         val destinationBounds = Rect()
-        if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
-            // The desktop task is currently occupying the whole stable bounds. If the bounds
-            // before the task was toggled to stable bounds were saved, toggle the task to those
-            // bounds. Otherwise, toggle to the default bounds.
+
+        val isMaximized = if (taskInfo.isResizeable) {
+            currentTaskBounds == stableBounds
+        } else {
+            currentTaskBounds.width() == stableBounds.width()
+                    || currentTaskBounds.height() == stableBounds.height()
+        }
+
+        if (isMaximized) {
+            // The desktop task is at the maximized width and/or height of the stable bounds.
+            // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
+            // Otherwise, toggle to the default bounds.
             val taskBoundsBeforeMaximize =
                 desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
             if (taskBoundsBeforeMaximize != null) {
@@ -670,9 +679,20 @@
         } else {
             // Save current bounds so that task can be restored back to original bounds if necessary
             // and toggle to the stable bounds.
-            val taskBounds = taskInfo.configuration.windowConfiguration.bounds
-            desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, taskBounds)
-            destinationBounds.set(stableBounds)
+            desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
+
+            if (taskInfo.isResizeable) {
+                // if resizable then expand to entire stable bounds (full display minus insets)
+                destinationBounds.set(stableBounds)
+            } else {
+                // if non-resizable then calculate max bounds according to aspect ratio
+                val activityAspectRatio = calculateAspectRatio(taskInfo)
+                val newSize = maximumSizeMaintainingAspectRatio(taskInfo,
+                    Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
+                val newBounds = centerInArea(
+                    newSize, stableBounds, stableBounds.left, stableBounds.top)
+                destinationBounds.set(newBounds)
+            }
         }
 
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
@@ -775,7 +795,7 @@
             addWallpaperActivity(wct)
         } else {
             // Move home to front
-            moveHomeTaskToFront(wct)
+            moveHomeTask(wct, true /* toTop */)
         }
 
         val nonMinimizedTasksOrderedFrontToBack =
@@ -801,11 +821,11 @@
         return taskToMinimize
     }
 
-    private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
+    private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
         shellTaskOrganizer
             .getRunningTasks(context.displayId)
             .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
-            ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
+            ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) }
     }
 
     private fun addWallpaperActivity(wct: WindowContainerTransaction) {
@@ -1020,6 +1040,9 @@
             )
             return WindowContainerTransaction().also { wct ->
                 addMoveToDesktopChanges(wct, task)
+                // In some launches home task is moved behind new task being launched. Make sure
+                // that's not the case for launches in desktop.
+                moveHomeTask(wct, false /* toTop */)
                 // Desktop Mode is already showing and we're launching a new Task - we might need to
                 // minimize another Task.
                 val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index dc449d1..a8346a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip;
 
 import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
@@ -625,6 +626,14 @@
                 }
             } else {
                 adjustedSourceRectHint.set(sourceRectHint);
+                if (isInPipDirection(direction)
+                        && rotationDelta == ROTATION_0
+                        && taskInfo.displayCutoutInsets != null) {
+                    // TODO: this is to special case the issues on Foldable device
+                    // with display cutout. This aligns with what's in SwipePipToHomeAnimator.
+                    adjustedSourceRectHint.offset(taskInfo.displayCutoutInsets.left,
+                            taskInfo.displayCutoutInsets.top);
+                }
             }
             final Rect sourceHintRectInsets = new Rect();
             if (!adjustedSourceRectHint.isEmpty()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 789f706..6a6e2ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -636,6 +636,13 @@
             return;
         }
 
+        // bail early if leash is null
+        if (mLeash == null) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "exitPip: leash is null");
+            return;
+        }
+
         final Rect destinationBounds = new Rect(getExitDestinationBounds());
         final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index ccbe94c..8d36db9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -127,8 +127,10 @@
 
     /**
      * Called when the Shell wants to start resizing Pip transition/animation.
+     *
+     * @param duration the suggested duration for resize animation.
      */
-    public void startResizeTransition(WindowContainerTransaction wct) {
+    public void startResizeTransition(WindowContainerTransaction wct, int duration) {
         // Default implementation does nothing.
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 5c561fe..88f9e4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -47,9 +47,17 @@
     @Nullable
     private Runnable mAnimationEndCallback;
     private RectEvaluator mRectEvaluator;
+
+    // Bounds relative to which scaling/cropping must be done.
     private final Rect mBaseBounds = new Rect();
+
+    // Bounds to animate from.
     private final Rect mStartBounds = new Rect();
+
+    // Target bounds.
     private final Rect mEndBounds = new Rect();
+
+    // Bounds updated by the evaluator as animator is running.
     private final Rect mAnimatedRect = new Rect();
     private final float mDelta;
 
@@ -84,7 +92,6 @@
         addListener(this);
         addUpdateListener(this);
         setEvaluator(mRectEvaluator);
-        // TODO: change this
         setDuration(duration);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 68202ef..06adad6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -352,6 +352,7 @@
         mPipBoundsAlgorithm.dump(pw, innerPrefix);
         mPipBoundsState.dump(pw, innerPrefix);
         mPipDisplayLayoutState.dump(pw, innerPrefix);
+        mPipTransitionState.dump(pw, innerPrefix);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index cec2469..e277a8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip2.phone;
 
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
 import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
 import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
 import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
@@ -25,6 +26,7 @@
 import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
 import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
 import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,6 +38,7 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.util.Preconditions;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -45,6 +48,7 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipPerfHintController;
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 
@@ -62,6 +66,7 @@
         PipTransitionState.PipTransitionStateChangedListener {
     private static final String TAG = "PipMotionHelper";
     private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
+    private static final String ANIMATING_BOUNDS_CHANGE = "animating_bounds_change";
     private static final boolean DEBUG = false;
 
     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
@@ -113,7 +118,7 @@
 
     /** SpringConfig to use for fling-then-spring animations. */
     private final PhysicsAnimator.SpringConfig mSpringConfig =
-            new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+            new PhysicsAnimator.SpringConfig(300f, DAMPING_RATIO_LOW_BOUNCY);
 
     /** SpringConfig used for animating into the dismiss region, matches the one in
      * {@link MagnetizedObject}. */
@@ -152,9 +157,16 @@
     private boolean mDismissalPending = false;
 
     /**
-     * Set to true if bounds change transition has been scheduled from PipMotionHelper.
+     * Set to true if bounds change transition has been scheduled from PipMotionHelper
+     * after animating is over.
      */
-    private boolean mWaitingForBoundsChangeTransition = false;
+    private boolean mWaitingForFlingTransition = false;
+
+    /**
+     * Set to true if bounds change transition has been scheduled from PipMotionHelper,
+     * and if the animation is supposed to run while transition is playing.
+     */
+    private boolean mWaitingToPlayBoundsChangeTransition = false;
 
     /**
      * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
@@ -634,6 +646,9 @@
         // The physics animation ended, though we may not necessarily be done animating, such as
         // when we're still dragging after moving out of the magnetic target.
         if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) {
+            // Update the earlier estimate on bounds we are animating towards, since physics
+            // animator is non-deterministic.
+            setAnimatingToBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
             // do not schedule resize if PiP is dismissing, which may cause app re-open to
             // mBounds instead of its normal bounds.
             Bundle extra = new Bundle();
@@ -673,6 +688,11 @@
      * Directly resizes the PiP to the given {@param bounds}.
      */
     private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+        if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+            // Do not carry out any resizing if we are dragging or physics animator is running.
+            return;
+        }
+
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: resizeAndAnimatePipUnchecked: toBounds=%s"
@@ -682,10 +702,11 @@
 
         // Intentionally resize here even if the current bounds match the destination bounds.
         // This is so all the proper callbacks are performed.
-
-        // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
-        //         TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
-        // setAnimatingToBounds(toBounds);
+        setAnimatingToBounds(toBounds);
+        Bundle extra = new Bundle();
+        extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true);
+        extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, duration);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
     }
 
     @Override
@@ -694,7 +715,11 @@
             @Nullable Bundle extra) {
         switch (newState) {
             case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
-                if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
+                mWaitingForFlingTransition = extra.getBoolean(FLING_BOUNDS_CHANGE);
+                mWaitingToPlayBoundsChangeTransition = extra.getBoolean(ANIMATING_BOUNDS_CHANGE);
+                if (!mWaitingForFlingTransition && !mWaitingToPlayBoundsChangeTransition) {
+                    break;
+                }
 
                 if (mPipBoundsState.getBounds().equals(
                         mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) {
@@ -709,30 +734,30 @@
                     break;
                 }
 
-                // If touch is turned off and we are in a fling animation, schedule a transition.
-                mWaitingForBoundsChangeTransition = true;
+                // Delay config until the end, if we are animating after scheduling the transition.
                 mPipScheduler.scheduleAnimateResizePip(
-                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+                        mPipBoundsState.getMotionBoundsState().getAnimatingToBounds(),
+                        mWaitingToPlayBoundsChangeTransition,
+                        extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+                                PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION));
                 break;
             case PipTransitionState.CHANGING_PIP_BOUNDS:
-                if (!mWaitingForBoundsChangeTransition) break;
-
-                // If bounds change transition was scheduled from this class, handle leash updates.
-                mWaitingForBoundsChangeTransition = false;
                 SurfaceControl.Transaction startTx = extra.getParcelable(
                         PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+                SurfaceControl.Transaction finishTx = extra.getParcelable(
+                        PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
                 Rect destinationBounds = extra.getParcelable(
                         PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
-                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
-                        destinationBounds.left, destinationBounds.top);
-                startTx.apply();
+                final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+                        PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
 
-                // All motion operations have actually finished, so make bounds cache updates.
-                settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
-                cleanUpHighPerfSessionMaybe();
-
-                // Signal that the transition is done - should update transition state by default.
-                mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+                if (mWaitingForFlingTransition) {
+                    mWaitingForFlingTransition = false;
+                    handleFlingTransition(startTx, finishTx, destinationBounds);
+                } else if (mWaitingToPlayBoundsChangeTransition) {
+                    mWaitingToPlayBoundsChangeTransition = false;
+                    startResizeAnimation(startTx, finishTx, destinationBounds, duration);
+                }
                 break;
             case PipTransitionState.EXITING_PIP:
                 // We need to force finish any local animators if about to leave PiP, to avoid
@@ -740,9 +765,46 @@
                 if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
                 cancelPhysicsAnimation();
                 settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+                break;
         }
     }
 
+    private void handleFlingTransition(SurfaceControl.Transaction startTx,
+            SurfaceControl.Transaction finishTx, Rect destinationBounds) {
+        startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+                destinationBounds.left, destinationBounds.top);
+        startTx.apply();
+
+        // All motion operations have actually finished, so make bounds cache updates.
+        settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+        cleanUpHighPerfSessionMaybe();
+
+        // Signal that the transition is done - should update transition state by default.
+        mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+    }
+
+    private void startResizeAnimation(SurfaceControl.Transaction startTx,
+            SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
+        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        Preconditions.checkState(pipLeash != null,
+                "No leash cached by mPipTransitionState=" + mPipTransitionState);
+
+        startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
+                mPipBoundsState.getBounds().height());
+
+        PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
+                startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
+                destinationBounds, duration, 0f /* angle */);
+        animator.setAnimationEndCallback(() -> {
+            mPipBoundsState.setBounds(destinationBounds);
+            // All motion operations have actually finished, so make bounds cache updates.
+            cleanUpHighPerfSessionMaybe();
+            // Signal that we are done with resize transition
+            mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+        });
+        animator.start();
+    }
+
     private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
         if (!animatingAfter) {
             // The physics animation ended, though we may not necessarily be done animating, such as
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 33e80bd..5b0ca18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -16,6 +16,7 @@
 package com.android.wm.shell.pip2.phone;
 
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -535,7 +536,8 @@
                 mWaitingForBoundsChangeTransition = true;
 
                 // Schedule PiP resize transition, but delay any config updates until very end.
-                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */);
+                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds,
+                        true /* configAtEnd */, PINCH_RESIZE_SNAP_DURATION);
                 break;
             case PipTransitionState.CHANGING_PIP_BOUNDS:
                 if (!mWaitingForBoundsChangeTransition) break;
@@ -550,12 +552,15 @@
                         PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
                 SurfaceControl.Transaction finishTx = extra.getParcelable(
                         PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+                final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+                        PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
                 startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
                         mPipBoundsState.getBounds().height());
 
                 PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
                         startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease,
-                        mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle);
+                        mLastResizeBounds, duration, mAngle);
                 animator.setAnimationEndCallback(() -> {
                     // All motion operations have actually finished, so make bounds cache updates.
                     mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 4f62192..ac670cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -162,6 +162,18 @@
      * @param configAtEnd true if we are delaying config updates until the transition ends.
      */
     public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) {
+        scheduleAnimateResizePip(toBounds, configAtEnd,
+                PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+    }
+
+    /**
+     * Animates resizing of the pinned stack given the duration.
+     *
+     * @param configAtEnd true if we are delaying config updates until the transition ends.
+     * @param duration    the suggested duration to run the animation; the component responsible
+     *                    for running the animator will get this as an extra.
+     */
+    public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
         if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
             return;
         }
@@ -170,7 +182,7 @@
         if (configAtEnd) {
             wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
         }
-        mPipTransitionController.startResizeTransition(wct);
+        mPipTransitionController.startResizeTransition(wct, duration);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index a9f3b54..0c4ed26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -713,15 +713,13 @@
         }
     }
 
-    private void animateToMaximizedState(Runnable callback) {
-        Rect maxMovementBounds = new Rect();
+    private void animateToMaximizedState() {
         Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
                 mPipBoundsState.getMaxSize().y);
-        mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
-                mIsImeShowing ? mImeHeight : 0);
+
         mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
-                mPipBoundsState.getMovementBounds(), maxMovementBounds,
-                callback);
+                getMovementBounds(mPipBoundsState.getBounds()),
+                getMovementBounds(maxBounds), null /* callback */);
     }
 
     private void animateToNormalSize(Runnable callback) {
@@ -729,22 +727,20 @@
         mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
 
         final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
-        final Rect normalBounds = mPipBoundsState.getNormalBounds();
-        final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
-                minMenuSize);
-        Rect restoredMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getMovementBounds(destBounds,
-                mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
-        mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
-                mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+        final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
+        final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
+        final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(
+                normalBounds, minMenuSize);
+
+        mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
+                getMovementBounds(mPipBoundsState.getBounds()),
+                getMovementBounds(adjustedNormalBounds), callback /* callback */);
     }
 
     private void animateToUnexpandedState(Rect restoreBounds) {
-        Rect restoredMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
-                mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
-                restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+                getMovementBounds(restoreBounds),
+                getMovementBounds(mPipBoundsState.getBounds()), false /* immediate */);
         mSavedSnapFraction = -1f;
     }
 
@@ -919,10 +915,6 @@
                     && mMenuState != MENU_STATE_FULL) {
                 // If using pinch to zoom, double-tap functions as resizing between max/min size
                 if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
-                    final boolean toExpand = mPipBoundsState.getBounds().width()
-                            < mPipBoundsState.getMaxSize().x
-                            && mPipBoundsState.getBounds().height()
-                            < mPipBoundsState.getMaxSize().y;
                     if (mMenuController.isMenuVisible()) {
                         mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
                     }
@@ -934,7 +926,7 @@
                     // actually toggle to the size chosen
                     if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
                         mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
-                        animateToMaximizedState(null);
+                        animateToMaximizedState();
                     } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
                         mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                         animateToNormalSize(null);
@@ -1045,7 +1037,9 @@
 
     private Rect getMovementBounds(Rect curBounds) {
         Rect movementBounds = new Rect();
-        mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+        Rect insetBounds = new Rect();
+        mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+        mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
                 movementBounds, mIsImeShowing ? mImeHeight : 0);
         return movementBounds;
     }
@@ -1091,6 +1085,7 @@
             case PipTransitionState.ENTERED_PIP:
                 onActivityPinned();
                 mTouchState.setAllowInputEvents(true);
+                mTouchState.reset();
                 break;
             case PipTransitionState.EXITED_PIP:
                 mTouchState.setAllowInputEvents(false);
@@ -1101,6 +1096,7 @@
                 break;
             case PipTransitionState.CHANGED_PIP_BOUNDS:
                 mTouchState.setAllowInputEvents(true);
+                mTouchState.reset();
                 break;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 57dc5f9..683d30d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -71,6 +71,9 @@
     static final String PIP_START_TX = "pip_start_tx";
     static final String PIP_FINISH_TX = "pip_finish_tx";
     static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds";
+    static final String ANIMATING_BOUNDS_CHANGE_DURATION =
+            "animating_bounds_change_duration";
+    static final int BOUNDS_CHANGE_JUMPCUT_DURATION = 0;
 
     /**
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -87,7 +90,7 @@
     private final PipTransitionState mPipTransitionState;
 
     //
-    // Transition tokens
+    // Transition caches
     //
 
     @Nullable
@@ -96,6 +99,8 @@
     private IBinder mExitViaExpandTransition;
     @Nullable
     private IBinder mResizeTransition;
+    private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+
 
     //
     // Internal state and relevant cached info
@@ -152,11 +157,12 @@
     }
 
     @Override
-    public void startResizeTransition(WindowContainerTransaction wct) {
+    public void startResizeTransition(WindowContainerTransaction wct, int duration) {
         if (wct == null) {
             return;
         }
         mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+        mBoundsChangeDuration = duration;
     }
 
     @Nullable
@@ -272,6 +278,10 @@
         extra.putParcelable(PIP_START_TX, startTransaction);
         extra.putParcelable(PIP_FINISH_TX, finishTransaction);
         extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds());
+        if (mBoundsChangeDuration > BOUNDS_CHANGE_JUMPCUT_DURATION) {
+            extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, mBoundsChangeDuration);
+            mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+        }
 
         mFinishCallback = finishCallback;
         mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 9d599ca..29272be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -29,6 +29,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -62,6 +63,8 @@
  * and throw an <code>IllegalStateException</code> otherwise.</p>
  */
 public class PipTransitionState {
+    private static final String TAG = PipTransitionState.class.getSimpleName();
+
     public static final int UNDEFINED = 0;
 
     // State for Launcher animating the swipe PiP to home animation.
@@ -190,8 +193,9 @@
                     "No extra bundle for " + stateToString(state) + " state.");
         }
         if (mState != state) {
-            dispatchPipTransitionStateChanged(mState, state, extra);
+            final int prevState = mState;
             mState = state;
+            dispatchPipTransitionStateChanged(prevState, mState, extra);
         }
     }
 
@@ -319,4 +323,11 @@
         return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
                 stateToString(mState), mInSwipePipToHomeTransition);
     }
+
+    /** Dumps internal state. */
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mState=" + stateToString(mState));
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 6325c68..ea8c0eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,8 @@
                                 SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
                                 TransactionPool transactionPool, Rect firstWindowFrame,
                                 int mainWindowShiftLength, float roundedCornerRadius) {
-            mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
+            mFromYDelta = firstWindowFrame.top
+                    - Math.max(firstWindowFrame.top, roundedCornerRadius);
             mToYDelta = toYDelta;
             mOccludeHoleView = occludeHoleView;
             mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index c93e584..ee6c81a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -21,7 +21,7 @@
 import com.android.internal.protolog.ProtoLog
 
 /**
- * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
+ * Log messages using an API similar to [com.android.internal.protolog.ProtoLog]. Useful for
  * logging from Kotlin classes as ProtoLog does not have support for Kotlin.
  *
  * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup].
@@ -29,42 +29,42 @@
 // TODO(b/168581922): remove once ProtoLog adds support for Kotlin
 class KtProtoLog {
     companion object {
-        /** @see [com.android.internal.protolog.common.ProtoLog.d] */
+        /** @see [com.android.internal.protolog.ProtoLog.d] */
         fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
             if (group.isLogToLogcat) {
                 Log.d(group.tag, String.format(messageString, *args))
             }
         }
 
-        /** @see [com.android.internal.protolog.common.ProtoLog.v] */
+        /** @see [com.android.internal.protolog.ProtoLog.v] */
         fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
             if (group.isLogToLogcat) {
                 Log.v(group.tag, String.format(messageString, *args))
             }
         }
 
-        /** @see [com.android.internal.protolog.common.ProtoLog.i] */
+        /** @see [com.android.internal.protolog.ProtoLog.i] */
         fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
             if (group.isLogToLogcat) {
                 Log.i(group.tag, String.format(messageString, *args))
             }
         }
 
-        /** @see [com.android.internal.protolog.common.ProtoLog.w] */
+        /** @see [com.android.internal.protolog.ProtoLog.w] */
         fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
             if (group.isLogToLogcat) {
                 Log.w(group.tag, String.format(messageString, *args))
             }
         }
 
-        /** @see [com.android.internal.protolog.common.ProtoLog.e] */
+        /** @see [com.android.internal.protolog.ProtoLog.e] */
         fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
             if (group.isLogToLogcat) {
                 Log.e(group.tag, String.format(messageString, *args))
             }
         }
 
-        /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
+        /** @see [com.android.internal.protolog.ProtoLog.wtf] */
         fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
             if (group.isLogToLogcat) {
                 Log.wtf(group.tag, String.format(messageString, *args))
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 f53c21d..2a95fa3 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 android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -600,12 +601,13 @@
             if (mAppIconBitmap != null && mAppName != null) {
                 return;
             }
-            final ActivityInfo activityInfo = mTaskInfo.topActivityInfo;
-            if (activityInfo == null) {
-                Log.e(TAG, "Top activity info not found in task");
+            final ComponentName baseActivity = mTaskInfo.baseActivity;
+            if (baseActivity == null) {
+                Log.e(TAG, "Base activity component not found in task");
                 return;
             }
-            PackageManager pm = mContext.getApplicationContext().getPackageManager();
+            final PackageManager pm = mContext.getApplicationContext().getPackageManager();
+            final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
             final IconProvider provider = new IconProvider(mContext);
             final Drawable appIconDrawable = provider.getIcon(activityInfo);
             final BaseIconFactory headerIconFactory = createIconFactory(mContext,
@@ -619,6 +621,8 @@
 
             final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
             mAppName = pm.getApplicationLabel(applicationInfo);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Base activity's component name cannot be found on the system");
         } finally {
             Trace.endSection();
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 35ed8de..7873a85 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -65,7 +65,7 @@
         setup {
             standardAppHelper.launchViaIntent(
                 wmHelper,
-                YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+                YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
                 ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
             )
             standardAppHelper.waitForVideoPlaying()
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 879034f..5c539a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -73,7 +73,7 @@
         setup {
             standardAppHelper.launchViaIntent(
                 wmHelper,
-                YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+                YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
                 ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
             )
             standardAppHelper.enterFullscreen()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index c671fbe..b5a6d83 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -48,6 +48,8 @@
     fun setup() {
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
+        // TODO: b/349075982 - Remove once launcher rotation and checks are stable.
+        tapl.setExpectedRotationCheckEnabled(false)
 
         SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
     }
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 5f36e9a..a3b0dc5 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
@@ -1087,13 +1087,17 @@
   fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
+    val homeTask = setUpHomeTask()
     val freeformTask = setUpFreeformTask()
     markTaskVisible(freeformTask)
     val fullscreenTask = createFullscreenTask()
 
-    val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-    assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+    val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+    assertThat(wct?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
         .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
+    wct.assertReorderAt(1, homeTask, toTop = false)
   }
 
   @Test
@@ -1916,6 +1920,26 @@
   }
 
   @Test
+  fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
+    val bounds = Rect(0, 0, 200, 100)
+    val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+      topActivityInfo = ActivityInfo().apply {
+        screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+        configuration.windowConfiguration.appBounds = bounds
+      }
+      isResizeable = false
+    }
+
+    // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
+    val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
+
+    controller.toggleDesktopTaskSize(task)
+    // Assert bounds set to stable bounds
+    val wct = getLatestToggleResizeDesktopTaskWct()
+    assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+  }
+
+  @Test
   fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
@@ -1942,6 +1966,46 @@
   }
 
   @Test
+  fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
+    val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+    val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+      isResizeable = false
+    }
+
+    // Maximize
+    controller.toggleDesktopTaskSize(task)
+    task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
+      boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
+
+    // Restore
+    controller.toggleDesktopTaskSize(task)
+
+    // Assert bounds set to last bounds before maximize
+    val wct = getLatestToggleResizeDesktopTaskWct()
+    assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+  }
+
+  @Test
+  fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
+    val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+    val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+      isResizeable = false
+    }
+
+    // Maximize
+    controller.toggleDesktopTaskSize(task)
+    task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
+      STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
+
+    // Restore
+    controller.toggleDesktopTaskSize(task)
+
+    // Assert bounds set to last bounds before maximize
+    val wct = getLatestToggleResizeDesktopTaskWct()
+    assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+  }
+
+  @Test
   fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
     val boundsBeforeMaximize = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 36e8a46..8165e59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -170,7 +170,7 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws PackageManager.NameNotFoundException {
         mMockitoSession = mockitoSession()
                 .strictness(Strictness.LENIENT)
                 .spyStatic(DesktopModeStatus.class)
@@ -186,6 +186,9 @@
         mTestableContext.ensureTestableResources();
         mContext.setMockPackageManager(mMockPackageManager);
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
+        final ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo);
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
@@ -608,8 +611,6 @@
                 .setTaskDescriptionBuilder(taskDescriptionBuilder)
                 .setVisible(visible)
                 .build();
-        taskInfo.topActivityInfo = new ActivityInfo();
-        taskInfo.topActivityInfo.applicationInfo = new ApplicationInfo();
         taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
                 "DesktopModeWindowDecorationTests");
         taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e134c23..124f1f0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7007,6 +7007,23 @@
     }
 
     /**
+     * Check whether a user can mute this stream type from a given UI element.
+     *
+     * <p>Only useful for volume controllers.
+     *
+     * @param streamType type of stream to check if it's mutable from UI
+     *
+     * @hide
+     */
+    public boolean isStreamMutableByUi(int streamType) {
+        try {
+            return getService().isStreamMutableByUi(streamType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Only useful for volume controllers.
      * @hide
      */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index c8b9da5..08cc126 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -305,6 +305,8 @@
 
     boolean isStreamAffectedByMute(int streamType);
 
+    boolean isStreamMutableByUi(int streamType);
+
     void disableSafeMediaVolume(String callingPackage);
 
     oneway void lowerVolumeToRs1(String callingPackage);
diff --git a/packages/InputDevices/AndroidManifest.xml b/packages/InputDevices/AndroidManifest.xml
index da8c0d3..01b0bc7 100644
--- a/packages/InputDevices/AndroidManifest.xml
+++ b/packages/InputDevices/AndroidManifest.xml
@@ -19,5 +19,23 @@
             <meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
                     android:resource="@xml/keyboard_layouts" />
         </receiver>
+
+        <receiver android:name=".KeyGlyphMapProvider"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.hardware.input.action.QUERY_KEYBOARD_GLYPH_MAPS" />
+            </intent-filter>
+            <meta-data android:name="android.hardware.input.metadata.KEYBOARD_GLYPH_MAPS"
+                       android:resource="@xml/keyboard_glyph_maps" />
+        </receiver>
+
+        <receiver android:name=".OverlayKeyGlyphMapProvider"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.hardware.input.action.QUERY_KEYBOARD_GLYPH_MAPS" />
+            </intent-filter>
+            <meta-data android:name="android.hardware.input.metadata.KEYBOARD_GLYPH_MAPS"
+                       android:resource="@xml/keyboard_glyph_maps_overlay" />
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/InputDevices/res/xml/keyboard_glyph_maps.xml b/packages/InputDevices/res/xml/keyboard_glyph_maps.xml
new file mode 100644
index 0000000..b7a6f3f
--- /dev/null
+++ b/packages/InputDevices/res/xml/keyboard_glyph_maps.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<keyboard-glyph-maps xmlns:android="http://schemas.android.com/apk/res/android">
+<!--    <key-glyph-map-->
+<!--        android:glyphMap="@xml/xyz_glyph_map"-->
+<!--        android:vendorId="0x1234"-->
+<!--        android:productId="0x3456" />-->
+</keyboard-glyph-maps>
\ No newline at end of file
diff --git a/packages/InputDevices/res/xml/keyboard_glyph_maps_overlay.xml b/packages/InputDevices/res/xml/keyboard_glyph_maps_overlay.xml
new file mode 100644
index 0000000..7c036eb
--- /dev/null
+++ b/packages/InputDevices/res/xml/keyboard_glyph_maps_overlay.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<keyboard-glyph-maps xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- NOTE: This is reserved for glyph maps to be provided using RRO. For providing glyph maps in
+     AOSP use key_glyph_maps.xml instead -->
+</keyboard-glyph-maps>
\ No newline at end of file
diff --git a/packages/InputDevices/src/com/android/inputdevices/KeyGlyphMapProvider.java b/packages/InputDevices/src/com/android/inputdevices/KeyGlyphMapProvider.java
new file mode 100644
index 0000000..6b508dd
--- /dev/null
+++ b/packages/InputDevices/src/com/android/inputdevices/KeyGlyphMapProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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.inputdevices;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class KeyGlyphMapProvider extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Nothing to do at this time.
+    }
+}
diff --git a/packages/InputDevices/src/com/android/inputdevices/OverlayKeyGlyphMapProvider.java b/packages/InputDevices/src/com/android/inputdevices/OverlayKeyGlyphMapProvider.java
new file mode 100644
index 0000000..13b5b42
--- /dev/null
+++ b/packages/InputDevices/src/com/android/inputdevices/OverlayKeyGlyphMapProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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.inputdevices;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class OverlayKeyGlyphMapProvider extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Nothing to do at this time.
+    }
+}
diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml
index 524a88a..affcca1 100644
--- a/packages/PackageInstaller/res/layout/install_content_view.xml
+++ b/packages/PackageInstaller/res/layout/install_content_view.xml
@@ -34,7 +34,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
+        style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
         android:text="@string/message_staging" />
 
     <ProgressBar
@@ -57,7 +57,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
+        style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
         android:text="@string/installing" />
 
     <ProgressBar
@@ -74,7 +74,7 @@
       android:id="@+id/install_confirm_question"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_confirm_question"
       android:visibility="invisible"
       android:scrollbars="vertical" />
@@ -83,7 +83,7 @@
       android:id="@+id/install_confirm_question_update"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_confirm_question_update"
       android:visibility="invisible"
       android:scrollbars="vertical" />
@@ -92,7 +92,7 @@
       android:id="@+id/install_success"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_done"
       android:visibility="invisible" />
 
@@ -100,7 +100,7 @@
       android:id="@+id/install_failed"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_failed"
       android:visibility="invisible" />
 
@@ -108,7 +108,7 @@
       android:id="@+id/install_failed_blocked"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_failed_blocked"
       android:visibility="invisible" />
 
@@ -116,7 +116,7 @@
       android:id="@+id/install_failed_conflict"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_failed_conflict"
       android:visibility="invisible" />
 
@@ -124,7 +124,7 @@
       android:id="@+id/install_failed_incompatible"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_failed_incompatible"
       android:visibility="invisible" />
 
@@ -132,7 +132,7 @@
       android:id="@+id/install_failed_invalid_apk"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@android:style/TextAppearance.Material.Subhead"
+      style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
       android:text="@string/install_failed_invalid_apk"
       android:visibility="invisible" />
 
diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
index 434e333..d5c5d8b 100644
--- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml
+++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
@@ -22,32 +22,32 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
-  <LinearLayout
-      xmlns:android="http://schemas.android.com/apk/res/android"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:theme="?android:attr/alertDialogTheme"
-      android:orientation="vertical"
-      android:paddingTop="8dp"
-      android:paddingStart="?android:attr/dialogPreferredPadding"
-      android:paddingEnd="?android:attr/dialogPreferredPadding"
-      android:clipToPadding="false">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="?android:attr/alertDialogTheme"
+        android:orientation="vertical"
+        android:paddingTop="8dp"
+        android:paddingStart="?android:attr/dialogPreferredPadding"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:clipToPadding="false">
 
-      <TextView
-          android:id="@+id/message"
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          style="@android:style/TextAppearance.Material.Subhead" />
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" />
 
-      <CheckBox
-          android:id="@+id/keepData"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginTop="8dp"
-          android:layout_marginStart="-8dp"
-          android:paddingLeft="8sp"
-          android:visibility="gone"
-          style="@android:style/TextAppearance.Material.Subhead" />
+        <CheckBox
+            android:id="@+id/keepData"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:layout_marginStart="-8dp"
+            android:paddingStart="8sp"
+            android:paddingEnd="0sp"
+            android:visibility="gone"
+            style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" />
 
-  </LinearLayout>
+    </LinearLayout>
 </ScrollView>
diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml
index a5b82b3..37588ad 100644
--- a/packages/PackageInstaller/res/values-night/themes.xml
+++ b/packages/PackageInstaller/res/values-night/themes.xml
@@ -19,7 +19,6 @@
 
     <style name="Theme.AlertDialogActivity"
         parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
-        <item name="alertDialogStyle">@style/AlertDialog</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowAnimationStyle">@null</item>
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index f5af510..aa48712 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -19,7 +19,6 @@
 
     <style name="Theme.AlertDialogActivity"
         parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
-        <item name="alertDialogStyle">@style/AlertDialog</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowAnimationStyle">@null</item>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e0398aa..824dd4a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -330,7 +330,8 @@
             // data we still want to count it as "installed".
             mAppInfo = mPm.getApplicationInfo(pkgName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES);
-            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
+            // If the package is archived, treat it as update case.
+            if (!mAppInfo.isArchived && (mAppInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
                 mAppInfo = null;
             }
         } catch (NameNotFoundException e) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 2e9b7b4..c260426 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -95,7 +95,21 @@
      */
     var stagedSessionId = SessionInfo.INVALID_ID
         private set
+
+    /**
+     * UID of the last caller of Pia. This can point to a 3P installer if it uses intents to install
+     * an APK, or receives a
+     * [STATUS_PENDING_USER_ACTION][PackageInstaller.STATUS_PENDING_USER_ACTION] status code.
+     * It may point to Pia, when it receives the STATUS_PENDING_USER_ACTION status code in case of
+     * an update-ownership change.
+     */
     private var callingUid = Process.INVALID_UID
+
+    /**
+     * UID of the origin of the installation. This UID is used to fetch the app-label of the
+     * source of the install, and also check whether the source app has the AppOp to install other
+     * apps.
+     */
     private var originatingUid = Process.INVALID_UID
     private var callingPackage: String? = null
     private var sessionStager: SessionStager? = null
@@ -135,60 +149,60 @@
         stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
 
         callingPackage = callerInfo.packageName
-
-        if (sessionId != SessionInfo.INVALID_ID) {
-            val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId)
-            callingPackage = sessionInfo?.getInstallerPackageName()
-            callingAttributionTag = sessionInfo?.getInstallerAttributionTag()
-        }
-
-        // Uid of the source package, coming from ActivityManager
         callingUid = callerInfo.uid
-        if (callingUid == Process.INVALID_UID) {
-            Log.e(LOG_TAG, "Could not determine the launching uid.")
-        }
+
         val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
-        // Uid of the source package, with a preference to uid from ApplicationInfo
-        originatingUid = sourceInfo?.uid ?: callingUid
-        appOpRequestInfo = AppOpRequestInfo(
-            getPackageNameForUid(context, originatingUid, callingPackage),
-            originatingUid, callingAttributionTag
-        )
-
-        if(localLogv) {
-            Log.i(LOG_TAG, "Intent: $intent\n" +
-                "sessionId: $sessionId\n" +
-                "staged sessionId: $stagedSessionId\n" +
-                "calling package: $callingPackage\n" +
-                "callingUid: $callingUid\n" +
-                "originatingUid: $originatingUid")
-        }
-
         if (callingUid == Process.INVALID_UID && sourceInfo == null) {
             // Caller's identity could not be determined. Abort the install
             Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null")
             return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
         }
 
+        originatingUid = callingUid
+        val sessionInfo: SessionInfo? =
+            if (sessionId != SessionInfo.INVALID_ID)
+                packageInstaller.getSessionInfo(sessionId)
+            else null
+        if (sessionInfo != null) {
+            callingAttributionTag = sessionInfo.installerAttributionTag
+            if (sessionInfo.originatingUid != Process.INVALID_UID) {
+                originatingUid = sessionInfo.originatingUid
+            }
+        }
+
+        appOpRequestInfo = AppOpRequestInfo(
+            getPackageNameForUid(context, originatingUid, callingPackage),
+            originatingUid,
+            callingAttributionTag
+        )
+
+        if (localLogv) {
+            Log.i(
+                LOG_TAG, "Intent: $intent\n" +
+                    "sessionId: $sessionId\n" +
+                    "staged sessionId: $stagedSessionId\n" +
+                    "calling package: $callingPackage\n" +
+                    "callingUid: $callingUid\n" +
+                    "originatingUid: $originatingUid\n" +
+                    "sourceInfo: $sourceInfo"
+            )
+        }
+
         if ((sessionId != SessionInfo.INVALID_ID
-                && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+                && !isCallerSessionOwner(packageInstaller, callingUid, sessionId))
             || (stagedSessionId != SessionInfo.INVALID_ID
                 && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
         ) {
-            Log.e(LOG_TAG, "UID is not the owner of the session:\n" +
-                "CallingUid: $originatingUid | SessionId: $sessionId\n" +
-                "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId")
+            Log.e(
+                LOG_TAG, "UID is not the owner of the session:\n" +
+                    "CallingUid: $callingUid | SessionId: $sessionId\n" +
+                    "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId"
+            )
             return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
         }
 
-        isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
-        if (!isInstallPermissionGrantedOrRequested(
-                context, callingUid, originatingUid, isTrustedSource
-            )
-        ) {
-            Log.e(LOG_TAG, "UID $originatingUid needs to declare " +
-                Manifest.permission.REQUEST_INSTALL_PACKAGES
-            )
+        isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, callingUid)
+        if (!isInstallPermissionGrantedOrRequested(context, callingUid, isTrustedSource)) {
             return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
         }
 
@@ -196,7 +210,7 @@
         if (restriction != null) {
             val adminSupportDetailsIntent =
                 devicePolicyManager!!.createAdminSupportIntent(restriction)
-            Log.e(LOG_TAG, "$restriction set in place. Cannot install." )
+            Log.e(LOG_TAG, "$restriction set in place. Cannot install.")
             return InstallAborted(
                 ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
             )
@@ -221,12 +235,12 @@
     private fun isInstallRequestFromTrustedSource(
         sourceInfo: ApplicationInfo?,
         intent: Intent,
-        originatingUid: Int,
+        callingUid: Int,
     ): Boolean {
         val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
         return (sourceInfo != null && sourceInfo.isPrivilegedApp
             && (isNotUnknownSource
-            || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+            || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)))
     }
 
     private fun getDevicePolicyRestrictions(): String? {
@@ -311,7 +325,7 @@
                             Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
                         ),
                         activityResultCode = Activity.RESULT_FIRST_USER,
-                        errorDialogType =  if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
+                        errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
                     )
                     return
                 }
@@ -392,8 +406,9 @@
                 Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
                 params.setSize(pfd.statSize)
             } catch (e: IOException) {
-                Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
-                    "Try only apk size.", e
+                Log.e(
+                    LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+                        "Try only apk size.", e
                 )
             }
         } else {
@@ -651,7 +666,11 @@
     private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? {
         if (isAppUpdating(pkgInfo)) {
             val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
-            val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage)
+
+            val originatingPackageName =
+                getPackageNameForUid(context, originatingUid, callingPackage)
+            val requestedUpdateOwnerLabel = getApplicationLabel(originatingPackageName)
+
             if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
                 && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
             ) {
@@ -754,14 +773,14 @@
     }
 
     private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
-        if (requestInfo.callingPackage == null) {
+        if (requestInfo.originatingPackage == null) {
             Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
             return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
         }
         // Shouldn't use static constant directly, see b/65534401.
         val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
         val appOpMode = appOpsManager!!.noteOpNoThrow(
-            appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+            appOpStr!!, requestInfo.originatingUid, requestInfo.originatingPackage,
             requestInfo.attributionTag, "Started package installation activity"
         )
         if (localLogv) {
@@ -772,20 +791,20 @@
             AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
                 if (appOpMode == AppOpsManager.MODE_DEFAULT) {
                     appOpsManager.setMode(
-                        appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+                        appOpStr, requestInfo.originatingUid, requestInfo.originatingPackage,
                         AppOpsManager.MODE_ERRORED
                     )
                 }
                 try {
                     val sourceInfo =
-                        packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+                        packageManager.getApplicationInfo(requestInfo.originatingPackage, 0)
                     val sourceAppSnippet = getAppSnippet(context, sourceInfo)
                     InstallUserActionRequired(
                         USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
-                        dialogMessage = requestInfo.callingPackage
+                        sourceApp = requestInfo.originatingPackage
                     )
                 } catch (e: PackageManager.NameNotFoundException) {
-                    Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+                    Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.originatingPackage)
                     InstallAborted(ABORT_REASON_INTERNAL_ERROR)
                 }
             }
@@ -827,8 +846,10 @@
                 setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
             } catch (e: PackageManager.NameNotFoundException) {
                 setStageBasedOnResult(
-                    PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
-                    null)
+                    PackageInstaller.STATUS_FAILURE,
+                    PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+                    null
+                )
             }
             return
         }
@@ -848,7 +869,8 @@
             }
         } catch (e: OutOfIdsException) {
             setStageBasedOnResult(
-                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null
+            )
             return
         }
         val broadcastIntent = Intent(BROADCAST_ACTION)
@@ -866,7 +888,8 @@
             Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
             packageInstaller.abandonSession(stagedSessionId)
             setStageBasedOnResult(
-                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null
+            )
         }
     }
 
@@ -876,9 +899,11 @@
         message: String?,
     ) {
         if (localLogv) {
-            Log.i(LOG_TAG, "Status code: $statusCode\n" +
-                "legacy status: $legacyStatus\n" +
-                "message: $message")
+            Log.i(
+                LOG_TAG, "Status code: $statusCode\n" +
+                    "legacy status: $legacyStatus\n" +
+                    "message: $message"
+            )
         }
         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
             val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
@@ -891,11 +916,12 @@
             _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
         } else {
             if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
-                _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+                _installResult.setValue(
+                    InstallFailed(appSnippet, statusCode, legacyStatus, message)
+                )
             } else {
                 _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
             }
-
         }
     }
 
@@ -939,7 +965,7 @@
 
     data class CallerInfo(val packageName: String?, val uid: Int)
     data class AppOpRequestInfo(
-        val callingPackage: String?,
+        val originatingPackage: String?,
         val originatingUid: Int,
         val attributionTag: String?,
     )
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index bbb9bca..5dd4d29 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -43,7 +43,10 @@
     val actionReason: Int,
     private val appSnippet: PackageUtil.AppSnippet? = null,
     val isAppUpdating: Boolean = false,
-    val dialogMessage: String? = null,
+    /**
+     * This holds either a package name or the app label of the install source.
+     */
+    val sourceApp: String? = null,
 ) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
 
     val appIcon: Drawable?
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
index bae6f68..8572852 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -128,18 +128,19 @@
 
     /**
      * @param context the [Context] object
-     * @param callingUid the UID of the caller who's permission is being checked
-     * @param originatingUid the UID from where install is being originated. This could be same as
-     * callingUid or it will be the UID of the package performing a session based install
-     * @param isTrustedSource whether install request is coming from a privileged app or an app that
-     * has [Manifest.permission.INSTALL_PACKAGES] permission granted
-     * @return `true` if the package is granted the said permission
+     * @param callingUid the UID of the caller of Pia
+     * @param isTrustedSource indicates whether install request is coming from a privileged app
+     * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or that has
+     * the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
+     *
+     * @return `true` if the package is either a system downloads provider, a document manager,
+     * a trusted source, or has declared the
+     * [REQUEST_INSTALL_PACKAGES][Manifest.permission.REQUEST_INSTALL_PACKAGES] in its manifest.
      */
     @JvmStatic
     fun isInstallPermissionGrantedOrRequested(
         context: Context,
         callingUid: Int,
-        originatingUid: Int,
         isTrustedSource: Boolean,
     ): Boolean {
         val isDocumentsManager =
@@ -148,19 +149,18 @@
             getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
 
         if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
-            val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+            val targetSdkVersion = getMaxTargetSdkVersionForUid(context, callingUid)
             if (targetSdkVersion < 0) {
-                // Invalid originating uid supplied. Abort install.
-                Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+                // Invalid calling uid supplied. Abort install.
+                Log.e(LOG_TAG, "Cannot get target SDK version for uid $callingUid")
                 return false
             } else if (targetSdkVersion >= Build.VERSION_CODES.O
                 && !isUidRequestingPermission(
-                    context.packageManager, originatingUid,
-                    Manifest.permission.REQUEST_INSTALL_PACKAGES
+                    context.packageManager, callingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES
                 )
             ) {
                 Log.e(
-                    LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+                    LOG_TAG, "Requesting uid " + callingUid + " needs to declare permission "
                         + Manifest.permission.REQUEST_INSTALL_PACKAGES
                 )
                 return false
@@ -204,13 +204,13 @@
      * @return `true` if the caller is the session owner
      */
     @JvmStatic
-    fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
-        if (originatingUid == Process.ROOT_UID) {
+    fun isCallerSessionOwner(pi: PackageInstaller, callingUid: Int, sessionId: Int): Boolean {
+        if (callingUid == Process.ROOT_UID) {
             return true
         }
         val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
         val installerUid = sessionInfo.getInstallerUid()
-        return originatingUid == installerUid
+        return callingUid == installerUid
     }
 
     /**
@@ -362,8 +362,8 @@
      * @return the packageName corresponding to a UID.
      */
     @JvmStatic
-    fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
-        if (sourceUid == Process.INVALID_UID) {
+    fun getPackageNameForUid(context: Context, uid: Int, preferredPkgName: String?): String? {
+        if (uid == Process.INVALID_UID) {
             return null
         }
         // If the sourceUid belongs to the system downloads provider, we explicitly return the
@@ -371,20 +371,21 @@
         // packages, resulting in uncertainty about which package will end up first in the list
         // of packages associated with this UID
         val pm = context.packageManager
-        val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+        val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, uid)
         if (systemDownloadProviderInfo != null) {
             return systemDownloadProviderInfo.packageName
         }
-        val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+
+        val packagesForUid = pm.getPackagesForUid(uid) ?: return null
         if (packagesForUid.size > 1) {
-            if (callingPackage != null) {
+            Log.i(LOG_TAG, "Multiple packages found for source uid $uid")
+            if (preferredPkgName != null) {
                 for (packageName in packagesForUid) {
-                    if (packageName == callingPackage) {
+                    if (packageName == preferredPkgName) {
                         return packageName
                     }
                 }
             }
-            Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
         }
         return packagesForUid[0]
     }
@@ -439,7 +440,7 @@
      */
     data class AppSnippet(var label: CharSequence?, var icon: Drawable?) {
         override fun toString(): String {
-            return "AppSnippet[label = ${label}, hasIcon = ${icon != null}]"
+            return "AppSnippet[label = $label, hasIcon = ${icon != null}]"
         }
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index a95137d..343a213 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -63,7 +63,7 @@
             .setMessage(R.string.untrusted_external_source_warning)
             .setPositiveButton(R.string.external_sources_settings,
                 (dialog, which) -> mInstallActionListener.sendUnknownAppsIntent(
-                    mDialogData.getDialogMessage()))
+                    mDialogData.getSourceApp()))
             .setNegativeButton(R.string.cancel,
                 (dialog, which) -> mInstallActionListener.onNegativeResponse(
                     mDialogData.getStageCode()))
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 99b1eec..e186590 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -64,7 +64,7 @@
 
         int positiveBtnTextRes;
         if (mDialogData.isAppUpdating()) {
-            if (mDialogData.getDialogMessage() != null) {
+            if (mDialogData.getSourceApp() != null) {
                 positiveBtnTextRes = R.string.update_anyway;
             } else {
                 positiveBtnTextRes = R.string.update;
@@ -88,9 +88,10 @@
         TextView viewToEnable;
         if (mDialogData.isAppUpdating()) {
             viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update);
-            String dialogMessage = mDialogData.getDialogMessage();
-            if (dialogMessage != null) {
-                viewToEnable.setText(Html.fromHtml(dialogMessage, Html.FROM_HTML_MODE_LEGACY));
+            String sourcePackageName = mDialogData.getSourceApp();
+            if (sourcePackageName != null) {
+                // Show the update-ownership change message
+                viewToEnable.setText(Html.fromHtml(sourcePackageName, Html.FROM_HTML_MODE_LEGACY));
             }
         } else {
             viewToEnable = dialogView.requireViewById(R.id.install_confirm_question);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
index 3f19830..960df63 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
@@ -20,6 +20,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.app.AutomaticZenRule;
 import android.content.Context;
@@ -120,7 +121,14 @@
     }
 
     private static Drawable getFallbackIcon(Context context, int ruleType) {
-        int iconResIdFromType = switch (ruleType) {
+        int iconResIdFromType = getIconResourceIdFromType(ruleType);
+        return requireNonNull(context.getDrawable(iconResIdFromType));
+    }
+
+    /** Return the default icon resource associated to a {@link AutomaticZenRule.Type} */
+    @DrawableRes
+    public static int getIconResourceIdFromType(@AutomaticZenRule.Type int ruleType) {
+        return switch (ruleType) {
             case AutomaticZenRule.TYPE_UNKNOWN ->
                     com.android.internal.R.drawable.ic_zen_mode_type_unknown;
             case AutomaticZenRule.TYPE_OTHER ->
@@ -141,7 +149,6 @@
                     com.android.internal.R.drawable.ic_zen_mode_type_managed;
             default -> com.android.internal.R.drawable.ic_zen_mode_type_unknown;
         };
-        return requireNonNull(context.getDrawable(iconResIdFromType));
     }
 
     private static Drawable getMonochromeIconIfPresent(Drawable icon) {
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 33d39f0..2cc9112 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -23,6 +23,7 @@
 import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
 import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
 import static java.util.Objects.requireNonNull;
@@ -33,12 +34,15 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.service.notification.SystemZenRules;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 import android.util.Log;
 
+import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -56,11 +60,12 @@
  * <p>It also adapts other rule features that we don't want to expose in the UI, such as
  * interruption filters other than {@code PRIORITY}, rules without specific icons, etc.
  */
-public class ZenMode {
+public class ZenMode implements Parcelable {
 
     private static final String TAG = "ZenMode";
 
     static final String MANUAL_DND_MODE_ID = "manual_dnd";
+    static final String TEMP_NEW_MODE_ID = "temp_new_mode";
 
     // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
     private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
@@ -125,6 +130,25 @@
                 isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
     }
 
+    /**
+     * Returns a new {@link ZenMode} instance that can represent a custom_manual mode that is in the
+     * process of being created (and not yet saved).
+     *
+     * @param name mode name
+     * @param iconResId resource id of the chosen icon, {code 0} if none.
+     */
+    public static ZenMode newCustomManual(String name, @DrawableRes int iconResId) {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder(name,
+                ZenModeConfig.toCustomManualConditionId())
+                .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName())
+                .setType(AutomaticZenRule.TYPE_OTHER)
+                .setOwner(ZenModeConfig.getCustomManualConditionProvider())
+                .setIconResId(iconResId)
+                .setManualInvocationAllowed(true)
+                .build();
+        return new ZenMode(TEMP_NEW_MODE_ID, rule, Status.ENABLED, false);
+    }
+
     private ZenMode(String id, @NonNull AutomaticZenRule rule, Status status, boolean isManualDnd) {
         mId = id;
         mRule = rule;
@@ -304,4 +328,34 @@
     public String toString() {
         return mId + " (" + mStatus + ") -> " + mRule;
     }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeParcelable(mRule, 0);
+        dest.writeString(mStatus.name());
+        dest.writeBoolean(mIsManualDnd);
+    }
+
+    public static final Creator<ZenMode> CREATOR = new Creator<ZenMode>() {
+        @Override
+        public ZenMode createFromParcel(Parcel in) {
+            return new ZenMode(
+                    in.readString(),
+                    checkNotNull(in.readParcelable(AutomaticZenRule.class.getClassLoader(),
+                            AutomaticZenRule.class)),
+                    Status.valueOf(in.readString()),
+                    in.readBoolean());
+        }
+
+        @Override
+        public ZenMode[] newArray(int size) {
+            return new ZenMode[size];
+        }
+    };
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index 5529da0..d36e2fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -27,6 +27,8 @@
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
 
+import androidx.annotation.DrawableRes;
+
 import com.android.settingslib.R;
 
 import java.time.Duration;
@@ -184,18 +186,13 @@
      * Creates a new custom mode with the provided {@code name}. The mode will be "manual" (i.e.
      * not have a schedule), this can be later updated by the user in the mode settings page.
      *
+     * @param name mode name
+     * @param iconResId resource id of the chosen icon, {code 0} if none.
      * @return the created mode. Only {@code null} if creation failed due to an internal error
      */
     @Nullable
-    public ZenMode addCustomMode(String name) {
-        AutomaticZenRule rule = new AutomaticZenRule.Builder(name,
-                ZenModeConfig.toCustomManualConditionId())
-                .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName())
-                .setType(AutomaticZenRule.TYPE_OTHER)
-                .setOwner(ZenModeConfig.getCustomManualConditionProvider())
-                .setManualInvocationAllowed(true)
-                .build();
-
+    public ZenMode addCustomManualMode(String name, @DrawableRes int iconResId) {
+        AutomaticZenRule rule = ZenMode.newCustomManual(name, iconResId).getRule();
         String ruleId = mNotificationManager.addAutomaticZenRule(rule);
         return getMode(ruleId);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 837c682..c88c4c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -182,7 +182,7 @@
             minVolume = getMinVolume(audioStream),
             maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
             volume = audioManager.getStreamVolume(audioStream.value),
-            isAffectedByMute = audioManager.isStreamAffectedByMute(audioStream.value),
+            isAffectedByMute = audioManager.isStreamMutableByUi(audioStream.value),
             isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
             isMuted = audioManager.isStreamMute(audioStream.value),
         )
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index 32cdb98..9e54312 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -21,13 +21,17 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.AutomaticZenRule;
 import android.net.Uri;
+import android.os.Parcel;
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
+import com.android.internal.R;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -161,6 +165,30 @@
         assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY);
     }
 
+    @Test
+    public void writeToParcel_equals() {
+        assertUnparceledIsEqualToOriginal("example",
+                new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false)));
+
+        assertUnparceledIsEqualToOriginal("dnd", ZenMode.manualDndMode(ZEN_RULE, true));
+
+        assertUnparceledIsEqualToOriginal("custom_manual",
+                ZenMode.newCustomManual("New mode", R.drawable.ic_zen_mode_type_immersive));
+    }
+
+    private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            ZenMode unparceled = ZenMode.CREATOR.createFromParcel(parcel);
+
+            assertWithMessage("Comparing " + type).that(unparceled).isEqualTo(original);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
     private static ZenModeConfig.ZenRule zenConfigRuleFor(AutomaticZenRule azr, boolean isActive) {
         ZenModeConfig.ZenRule zenRule = new ZenModeConfig.ZenRule();
         zenRule.pkg = azr.getPackageName();
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 097840e..5ddf005 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -45,7 +45,7 @@
     <integer name="def_location_mode">3</integer>
     <!-- 0 == off, 1 == on-->
     <integer name="def_paired_device_location_mode">1</integer>
-    <bool name="assisted_gps_enabled">true</bool>
+    <bool name="assisted_gps_enabled">false</bool>
     <bool name="def_netstats_enabled">true</bool>
     <bool name="def_usb_mass_storage_enabled">true</bool>
     <bool name="def_wifi_on">false</bool>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bd7c9a0..b5776e2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -193,7 +193,7 @@
         "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
         "tests/src/**/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt",
         "tests/src/**/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt",
-        "tests/src/**/systemui/navigationbar/NavigationBarButtonTest.java",
+        "tests/src/**/systemui/navigationbar/views/NavigationBarButtonTest.java",
         "tests/src/**/systemui/people/PeopleProviderTest.java",
         "tests/src/**/systemui/people/PeopleSpaceUtilsTest.java",
         "tests/src/**/systemui/people/widget/PeopleSpaceWidgetManagerTest.java",
@@ -449,7 +449,7 @@
         "tests/src/**/systemui/doze/DozeScreenStateTest.java",
         "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
         "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java",
-        "tests/src/**/systemui/navigationbar/NavigationBarTest.java",
+        "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
         "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
         "tests/src/**/systemui/power/PowerUITest.java",
         "tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b37db16..666d939 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -280,6 +280,9 @@
     <!-- to adjust volume in volume panel -->
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
 
+    <!-- to get bluetooth audio device category -->
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
+
     <!-- to access ResolverRankerServices -->
     <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f5153e1..83b7455 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -146,6 +146,16 @@
 }
 
 flag {
+   name: "notification_transparent_header_fix"
+   namespace: "systemui"
+   description: "fix the transparent group header issue for async header inflation."
+   bug: "340161724"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "pss_app_selector_abrupt_exit_fix"
    namespace: "systemui"
    description: "Fixes the app selector abruptly disappearing without an animation, when the"
@@ -1045,14 +1055,6 @@
   bug: "343505271"
 }
 
-
-flag {
-  name: "new_touchpad_gestures_tutorial"
-  namespace: "systemui"
-  description: "Enables new interactive tutorial for learning touchpad gestures"
-  bug: "309928033"
-}
-
 flag {
    name: "register_wallpaper_notifier_background"
    namespace: "systemui"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index e02e5f8..bb76c1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -160,7 +160,7 @@
         viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
     val backgroundType by
         viewModel.communalBackground.collectAsStateWithLifecycle(
-            initialValue = CommunalBackgroundType.DEFAULT
+            initialValue = CommunalBackgroundType.ANIMATED
         )
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
@@ -243,7 +243,7 @@
 ) {
     Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) {
         when (backgroundType) {
-            CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors)
+            CommunalBackgroundType.STATIC -> DefaultBackground(colors = colors)
             CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient()
             CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient()
             CommunalBackgroundType.NONE -> BackgroundTopScrim()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 4eae14b3..07898b0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -108,7 +108,12 @@
     private val draggingItemLayoutInfo: LazyGridItemInfo?
         get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
 
-    internal fun onDragStart(offset: Offset, contentOffset: Offset) {
+    /**
+     * Called when dragging is initiated.
+     *
+     * @return {@code True} if dragging a grid item, {@code False} otherwise.
+     */
+    internal fun onDragStart(offset: Offset, contentOffset: Offset): Boolean {
         state.layoutInfo.visibleItemsInfo
             .filter { item -> contentListState.isItemEditable(item.index) }
             // grid item offset is based off grid content container so we need to deduct
@@ -118,7 +123,10 @@
                 dragStartPointerOffset = offset - this.offset.toOffset()
                 draggingItemIndex = index
                 draggingItemInitialOffset = this.offset.toOffset()
+                return true
             }
+
+        return false
     }
 
     internal fun onDragInterrupted() {
@@ -216,8 +224,9 @@
                     dragDropState.onDrag(offset = offset)
                 },
                 onDragStart = { offset ->
-                    dragDropState.onDragStart(offset, contentOffset)
-                    viewModel.onReorderWidgetStart()
+                    if (dragDropState.onDragStart(offset, contentOffset)) {
+                        viewModel.onReorderWidgetStart()
+                    }
                 },
                 onDragEnd = {
                     dragDropState.onDragInterrupted()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
index 418df5c..0b96694 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
@@ -89,7 +89,7 @@
 
         // Content.
         val contentColor = LocalAndroidColorScheme.current.onSurfaceVariant
-        Box(Modifier.defaultMinSize(minHeight = 48.dp)) {
+        Box {
             CompositionLocalProvider(LocalContentColor provides contentColor) {
                 ProvideTextStyle(
                     MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.Center)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2eea2f0..a568824 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,13 +19,19 @@
 
 import android.util.Log
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.absoluteOffset
 import androidx.compose.foundation.layout.asPaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -41,9 +47,11 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -52,6 +60,7 @@
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
@@ -59,10 +68,12 @@
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -71,6 +82,7 @@
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
+import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
@@ -137,6 +149,87 @@
     )
 }
 
+/**
+ * A version of [HeadsUpNotificationSpace] that can be swiped up off the top edge of the screen by
+ * the user. When swiped up, the heads up notification is snoozed.
+ */
+@Composable
+fun SceneScope.SnoozeableHeadsUpNotificationSpace(
+    stackScrollView: NotificationScrollView,
+    viewModel: NotificationsPlaceholderViewModel,
+) {
+    val context = LocalContext.current
+    val density = LocalDensity.current
+    val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
+    val headsUpPadding =
+        with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
+
+    val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
+
+    var scrollOffset by remember { mutableFloatStateOf(0f) }
+    val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+    val maxScrollOffset = 0f
+
+    val scrollableState = rememberScrollableState { delta ->
+        consumeDeltaWithinRange(
+            current = scrollOffset,
+            setCurrent = { scrollOffset = it },
+            min = minScrollOffset,
+            max = maxScrollOffset,
+            delta
+        )
+    }
+
+    val nestedScrollConnection =
+        object : NestedScrollConnection {
+            override suspend fun onPreFling(available: Velocity): Velocity {
+                if (
+                    velocityOrPositionalThresholdReached(scrollOffset, minScrollOffset, available.y)
+                ) {
+                    scrollableState.animateScrollBy(minScrollOffset, tween())
+                } else {
+                    scrollableState.animateScrollBy(-minScrollOffset, tween())
+                }
+                return available
+            }
+        }
+
+    LaunchedEffect(isHeadsUp) { scrollOffset = 0f }
+
+    LaunchedEffect(scrollableState.isScrollInProgress) {
+        if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+            viewModel.snoozeHun()
+        }
+    }
+
+    HeadsUpNotificationSpace(
+        stackScrollView = stackScrollView,
+        viewModel = viewModel,
+        modifier =
+            Modifier.absoluteOffset {
+                    IntOffset(
+                        x = 0,
+                        y =
+                            calculateHeadsUpPlaceholderYOffset(
+                                scrollOffset.roundToInt(),
+                                minScrollOffset.roundToInt(),
+                                stackScrollView.topHeadsUpHeight
+                            )
+                    )
+                }
+                .thenIf(isHeadsUp) {
+                    Modifier.verticalNestedScrollToScene(
+                            bottomBehavior = NestedScrollBehavior.EdgeAlways
+                        )
+                        .nestedScroll(nestedScrollConnection)
+                        .scrollable(
+                            orientation = Orientation.Vertical,
+                            state = scrollableState,
+                        )
+                }
+    )
+}
+
 /** Adds the space where notification stack should appear in the scene. */
 @Composable
 fun SceneScope.ConstrainedNotificationStack(
@@ -480,6 +573,47 @@
     }
 }
 
+private fun calculateHeadsUpPlaceholderYOffset(
+    scrollOffset: Int,
+    minScrollOffset: Int,
+    topHeadsUpHeight: Int,
+): Int {
+    return -minScrollOffset +
+        (scrollOffset * (-minScrollOffset + topHeadsUpHeight) / -minScrollOffset)
+}
+
+private fun velocityOrPositionalThresholdReached(
+    scrollOffset: Float,
+    minScrollOffset: Float,
+    availableVelocityY: Float,
+): Boolean {
+    return availableVelocityY < HUN_SNOOZE_VELOCITY_THRESHOLD ||
+        (availableVelocityY <= 0f &&
+            scrollOffset < minScrollOffset * HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION)
+}
+
+/**
+ * Takes a range, current value, and delta, and updates the current value by the delta, coercing the
+ * result within the given range. Returns how much of the delta was consumed.
+ */
+private fun consumeDeltaWithinRange(
+    current: Float,
+    setCurrent: (Float) -> Unit,
+    min: Float,
+    max: Float,
+    delta: Float
+): Float {
+    return if (delta < 0 && current > min) {
+        val remainder = (current + delta - min).coerceAtMost(0f)
+        setCurrent((current + delta).coerceAtLeast(min))
+        delta - remainder
+    } else if (delta > 0 && current < max) {
+        val remainder = (current + delta).coerceAtLeast(0f)
+        setCurrent((current + delta).coerceAtMost(max))
+        delta - remainder
+    } else 0f
+}
+
 private inline fun debugLog(
     viewModel: NotificationsPlaceholderViewModel,
     msg: () -> Any,
@@ -514,3 +648,5 @@
 private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
 private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
 private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
+private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f
+private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index c066ae5..422c9f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -16,14 +16,14 @@
 
 package com.android.systemui.qs.ui.composable
 
-import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -46,6 +46,9 @@
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
 import com.android.systemui.qs.panels.ui.compose.EditMode
 import com.android.systemui.qs.panels.ui.compose.TileGrid
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutEnter
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutExit
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
@@ -95,7 +98,7 @@
                 )
 
                 ShadeBody(
-                    viewModel = viewModel,
+                    viewModel = viewModel.quickSettingsContainerViewModel,
                 )
             }
         }
@@ -104,39 +107,30 @@
 
 @Composable
 private fun ShadeBody(
-    viewModel: QuickSettingsShadeSceneViewModel,
+    viewModel: QuickSettingsContainerViewModel,
 ) {
     val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
 
-    Box {
-        // The main Quick Settings grid layout.
-        AnimatedVisibility(
-            visible = !isEditing,
-            enter = QuickSettingsShade.Transitions.QuickSettingsLayoutEnter,
-            exit = QuickSettingsShade.Transitions.QuickSettingsLayoutExit,
-        ) {
-            QuickSettingsLayout(
-                viewModel = viewModel,
-            )
-        }
-
-        // The Quick Settings Editor layout.
-        AnimatedVisibility(
-            visible = isEditing,
-            enter = QuickSettingsShade.Transitions.QuickSettingsEditorEnter,
-            exit = QuickSettingsShade.Transitions.QuickSettingsEditorExit,
-        ) {
+    AnimatedContent(
+        targetState = isEditing,
+        transitionSpec = { QuickSettingsLayoutEnter togetherWith QuickSettingsLayoutExit }
+    ) { editing ->
+        if (editing) {
             EditMode(
                 viewModel = viewModel.editModeViewModel,
                 modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding)
             )
+        } else {
+            QuickSettingsLayout(
+                viewModel = viewModel,
+            )
         }
     }
 }
 
 @Composable
 private fun QuickSettingsLayout(
-    viewModel: QuickSettingsShadeSceneViewModel,
+    viewModel: QuickSettingsContainerViewModel,
     modifier: Modifier = Modifier,
 ) {
     Column(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 4e334c2..a44041a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,26 +17,19 @@
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.absoluteOffset
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.IntOffset
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneDpAsState
 import com.android.compose.animation.scene.animateSceneFloatAsState
-import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
-import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -72,28 +65,9 @@
         )
         animateSceneDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false)
         Spacer(modifier.fillMaxSize())
-        HeadsUpNotificationStack(
+        SnoozeableHeadsUpNotificationSpace(
             stackScrollView = notificationStackScrolLView.get(),
             viewModel = notificationsPlaceholderViewModel
         )
     }
 }
-
-@Composable
-private fun SceneScope.HeadsUpNotificationStack(
-    stackScrollView: NotificationScrollView,
-    viewModel: NotificationsPlaceholderViewModel,
-) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-    val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
-    val headsUpPadding =
-        with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
-    HeadsUpNotificationSpace(
-        stackScrollView = stackScrollView,
-        viewModel = viewModel,
-        modifier =
-            Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) }
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index fbf91b7..a23bb67 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -48,6 +48,7 @@
 import com.android.compose.PlatformSliderColors
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
 
 @Composable
@@ -62,7 +63,7 @@
     val value by valueState(state)
     PlatformSlider(
         modifier =
-            modifier.clearAndSetSemantics {
+            modifier.sysuiResTag(state.label).clearAndSetSemantics {
                 if (state.isEnabled) {
                     contentDescription = state.label
                     state.a11yClickDescription?.let {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index ab14911..9da2a1b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -25,20 +25,25 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 
+/** Same as android.platform.systemui_tapl.ui.VolumePanel#VolumePanelTestTag */
+private const val VolumePanelTestTag = "VolumePanel"
 private val padding = 24.dp
 
 @Composable
+@OptIn(ExperimentalComposeUiApi::class)
 fun VolumePanelRoot(
     viewModel: VolumePanelViewModel,
     modifier: Modifier = Modifier,
@@ -52,6 +57,7 @@
             Components(
                 componentsState,
                 modifier
+                    .sysuiResTag(VolumePanelTestTag)
                     .semantics { paneTitle = accessibilityTitle }
                     .padding(
                         start = padding,
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 2fd7f1b..525839d 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -128,7 +128,7 @@
         errorLine1="        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());"
         errorLine2="                                                                ~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
             line="807"
             column="65"/>
     </issue>
@@ -1646,7 +1646,7 @@
         errorLine1="        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);"
         errorLine2="                                               ~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
             line="177"
             column="48"/>
     </issue>
@@ -2086,7 +2086,7 @@
         errorLine1="            WindowManager wm = getContext().getSystemService(WindowManager.class);"
         errorLine2="                                            ~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
             line="729"
             column="45"/>
     </issue>
@@ -2097,7 +2097,7 @@
         errorLine1="        WindowManager wm = getContext().getSystemService(WindowManager.class);"
         errorLine2="                                        ~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
             line="830"
             column="41"/>
     </issue>
@@ -4049,7 +4049,7 @@
         errorLine1="        pw.println(String.format(&quot;      mCurrentView: id=%s (%dx%d) %s %f&quot;,"
         errorLine2="                   ^">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
             line="1128"
             column="20"/>
     </issue>
@@ -4060,7 +4060,7 @@
         errorLine1="        pw.println(String.format(&quot;      disabled=0x%08x vertical=%s darkIntensity=%.2f&quot;,"
         errorLine2="                   ^">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
             line="1134"
             column="20"/>
     </issue>
@@ -4668,7 +4668,7 @@
         errorLine1="                    500).show();"
         errorLine2="                    ~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
             line="1894"
             column="21"/>
     </issue>
@@ -8386,7 +8386,7 @@
         errorLine1="        mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);"
         errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
             line="1500"
             column="60"/>
     </issue>
@@ -23055,7 +23055,7 @@
         errorLine1="            mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));"
         errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
             line="1001"
             column="48"/>
     </issue>
@@ -25002,7 +25002,7 @@
         errorLine1="        mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());"
         errorLine2="                                                                     ~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
             line="373"
             column="70"/>
     </issue>
@@ -25222,7 +25222,7 @@
         errorLine1="        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);"
         errorLine2="                                                                         ~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java"
             line="329"
             column="74"/>
     </issue>
@@ -25233,7 +25233,7 @@
         errorLine1="        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);"
         errorLine2="                                                                         ~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java"
             line="357"
             column="74"/>
     </issue>
@@ -25827,7 +25827,7 @@
         errorLine1="        new AsyncTask&lt;Icon, Void, Drawable>() {"
         errorLine2="        ^">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
             line="207"
             column="9"/>
     </issue>
@@ -29303,7 +29303,7 @@
         errorLine1="    public boolean onTouchEvent(MotionEvent ev) {"
         errorLine2="                   ~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
             line="267"
             column="20"/>
     </issue>
@@ -29578,7 +29578,7 @@
         errorLine1="        mView.setOnTouchListener(this::onNavigationTouch);"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
             line="782"
             column="9"/>
     </issue>
@@ -29589,7 +29589,7 @@
         errorLine1="    public boolean onTouchEvent(MotionEvent event) {"
         errorLine2="                   ~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
             line="383"
             column="20"/>
     </issue>
@@ -29600,7 +29600,7 @@
         errorLine1="    public boolean onTouchEvent(MotionEvent event) {"
         errorLine2="                   ~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java"
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java"
             line="207"
             column="20"/>
     </issue>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index fa79ea0..54e0725 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1284,6 +1284,41 @@
     }
 
     @Test
+    public void onAodDownAndDownTouchReceived() throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+                        -1 /* pointerId */, touchData);
+
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN fingerprint is requested because of AOD interrupt
+        // GIVEN there's been an AoD interrupt
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+        mScreenObserver.onScreenTurnedOn();
+        mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+        mFgExecutor.runAllReady();
+
+        // and an ACTION_DOWN is received and touch is within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
+        MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent);
+        mBiometricExecutor.runAllReady();
+        firstDownEvent.recycle();
+
+        // THEN the touch is only processed once
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+                anyBoolean());
+    }
+
+    @Test
     public void onTouch_pilferPointerWhenAltBouncerShowing()
             throws RemoteException {
         final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index fbe2c2e..cf14547 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -451,24 +451,6 @@
             }
         }
 
-    @Test
-    fun transitionFromDozingToGlanceableHub_forcesCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    testScope = this
-                )
-
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
     private fun TestScope.updateDocked(docked: Boolean) =
         with(kosmos) {
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index da40f64..0169f99 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -185,7 +185,7 @@
     fun backgroundType_defaultValue() =
         testScope.runTest {
             val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
-            assertThat(backgroundType).isEqualTo(CommunalBackgroundType.DEFAULT)
+            assertThat(backgroundType).isEqualTo(CommunalBackgroundType.ANIMATED)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 2694cab..d338774 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -69,6 +69,11 @@
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
+import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.shade.ShadeTestUtil
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -492,13 +497,16 @@
                 flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
             )
             // Transitioned to Glanceable hub.
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope = testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GLANCEABLE_HUB,
+                    )
             )
             // Shade not expanded.
-            shadeTestUtil.setLockscreenShadeExpansion(0f)
+            if (!SceneContainerFlag.isEnabled) shadeTestUtil.setLockscreenShadeExpansion(0f)
 
             assertThat(isFocusable).isEqualTo(true)
         }
@@ -541,10 +549,13 @@
             keyguardRepository.setKeyguardOccluded(true)
 
             // And on hub
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.DREAMING,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope = testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.DREAMING,
+                        to = KeyguardState.GLANCEABLE_HUB,
+                    )
             )
 
             // Then flow is not frozen
@@ -558,10 +569,13 @@
             assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
 
             // 3. When transitioned to OCCLUDED and activity shows
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GLANCEABLE_HUB,
-                to = KeyguardState.OCCLUDED,
-                testScope = testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.GLANCEABLE_HUB,
+                        to = KeyguardState.OCCLUDED,
+                    )
             )
 
             // Then flow is not frozen
@@ -579,10 +593,13 @@
             keyguardRepository.setKeyguardOccluded(false)
 
             // And transitioned to hub
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope = testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GLANCEABLE_HUB,
+                    )
             )
 
             // Then flow is not frozen
@@ -593,30 +610,30 @@
             runCurrent()
 
             // And transitioning to occluded
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.OCCLUDED,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-
-            keyguardTransitionRepository.sendTransitionStep(
-                from = KeyguardState.GLANCEABLE_HUB,
-                to = KeyguardState.OCCLUDED,
-                transitionState = TransitionState.RUNNING,
-                value = 0.5f,
+            kosmos.setTransition(
+                sceneTransition = Transition(from = Scenes.Communal, to = Scenes.Lockscreen),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.GLANCEABLE_HUB,
+                        to = KeyguardState.OCCLUDED,
+                        transitionState = TransitionState.STARTED,
+                        value = 0f,
+                    )
             )
 
             // Then flow is frozen
             assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
 
             // 3. When transition is finished
-            keyguardTransitionRepository.sendTransitionStep(
-                from = KeyguardState.GLANCEABLE_HUB,
-                to = KeyguardState.OCCLUDED,
-                transitionState = TransitionState.FINISHED,
-                value = 1f,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.GLANCEABLE_HUB,
+                        to = KeyguardState.OCCLUDED,
+                        transitionState = TransitionState.FINISHED,
+                        value = 1f,
+                    )
             )
 
             // Then flow is not frozen
@@ -639,10 +656,13 @@
             keyguardRepository.setKeyguardOccluded(true)
 
             // And transitioned to hub
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.DREAMING,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope = testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.DREAMING,
+                        to = KeyguardState.GLANCEABLE_HUB,
+                    )
             )
 
             // Widgets available
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c48ced1..86a99e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -149,6 +149,8 @@
         when(mDreamOverlayContainerView.getRootSurfaceControl())
                 .thenReturn(mAttachedSurfaceControl);
         when(mKeyguardTransitionInteractor.isFinishedInStateWhere(any())).thenReturn(emptyFlow());
+        when(mKeyguardTransitionInteractor.isFinishedIn(any(), any())).thenReturn(emptyFlow());
+        when(mKeyguardTransitionInteractor.isFinishedIn(any())).thenReturn(emptyFlow());
         when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(false));
         when(mCommunalInteractor.isCommunalShowing()).thenReturn(MutableStateFlow(false));
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 0792a50..612f2e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -34,14 +34,12 @@
 
 import android.os.PowerManager
 import android.platform.test.annotations.EnableFlags
-import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -66,10 +64,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
-import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -124,66 +120,6 @@
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToLockscreen_onWakeup_canDream_glanceableHubAvailable() =
-        testScope.runTest {
-            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
-            powerInteractor.setAwakeForTest()
-            runCurrent()
-
-            // If dreaming is possible and communal is available, then we should transition to
-            // GLANCEABLE_HUB when waking up.
-            assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                )
-        }
-
-    @Test
-    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToLockscreen_onWakeup_canNotDream_glanceableHubAvailable() =
-        testScope.runTest {
-            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
-            powerInteractor.setAwakeForTest()
-            runCurrent()
-
-            // If dreaming is NOT possible but communal is available, then we should transition to
-            // LOCKSCREEN when waking up.
-            assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.LOCKSCREEN,
-                )
-        }
-
-    @Test
-    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToLockscreen_onWakeup_canNDream_glanceableHubNotAvailable() =
-        testScope.runTest {
-            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
-            kosmos.setCommunalAvailable(false)
-            runCurrent()
-
-            powerInteractor.setAwakeForTest()
-            runCurrent()
-
-            // If dreaming is possible but communal is NOT available, then we should transition to
-            // LOCKSCREEN when waking up.
-            assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.LOCKSCREEN,
-                )
-        }
-
-    @Test
-    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
         testScope.runTest {
             kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0f061de..40918a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -1097,9 +1097,10 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun isFinishedInState() =
         testScope.runTest {
-            val results by collectValues(underTest.isFinishedInState(GONE))
+            val results by collectValues(underTest.isFinishedIn(Scenes.Gone, GONE))
 
             sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
@@ -1194,6 +1195,89 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun isFinishedIn() =
+        testScope.runTest {
+            val results by collectValues(underTest.isFinishedIn(Scenes.Gone, GONE))
+
+            sendSteps(
+                TransitionStep(AOD, DOZING, 0f, STARTED),
+                TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+                TransitionStep(AOD, DOZING, 1f, FINISHED),
+            )
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false, // Finished in DOZING, not GONE.
+                    )
+                )
+
+            kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
+
+            kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
+
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
+
+            kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
+
+    @Test
     fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
         testScope.runTest {
             val finishedStates by collectValues(underTest.finishedKeyguardState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/model/KeyguardStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/model/KeyguardStateTest.kt
new file mode 100644
index 0000000..f1ffe66
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/model/KeyguardStateTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.keyguard.shared.model
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardStateTest : SysuiTestCase() {
+
+    /**
+     * This test makes sure that the result of [deviceIsAwakeInState] are equal for all the states
+     * that are obsolete with scene container enabled and UNDEFINED. This means for example that if
+     * GONE is transformed to UNDEFINED it makes sure that GONE and UNDEFINED need to have the same
+     * value. This assumption is important as with scene container flag enabled call sites will only
+     * check the result passing in UNDEFINED.
+     *
+     * This is true today, but as more states may become obsolete this assumption may not be true
+     * anymore and therefore [deviceIsAwakeInState] would need to be rewritten to factor in the
+     * scene state.
+     */
+    @Test
+    @EnableSceneContainer
+    fun assertUndefinedResultMatchesObsoleteStateResults() {
+        for (state in KeyguardState.entries) {
+            val isAwakeInSceneContainer =
+                KeyguardState.deviceIsAwakeInState(state.mapToSceneContainerState())
+            val isAwake = KeyguardState.deviceIsAwakeInState(state)
+            assertThat(isAwakeInSceneContainer).isEqualTo(isAwake)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt
new file mode 100644
index 0000000..ae6f576
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QuickQuickSettingsRowRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val underTest = kosmos.quickQuickSettingsRowRepository
+
+    @Test
+    fun rows_followsConfig() =
+        with(kosmos) {
+            testScope.runTest {
+                val rows by collectLastValue(underTest.rows)
+
+                setRowsInConfig(2)
+                assertThat(rows).isEqualTo(2)
+
+                setRowsInConfig(3)
+                assertThat(rows).isEqualTo(3)
+            }
+        }
+
+    private fun setRowsInConfig(rows: Int) =
+        with(kosmos) {
+            testCase.context.orCreateTestableResources.addOverride(
+                R.integer.quick_qs_panel_max_rows,
+                rows,
+            )
+            fakeConfigurationRepository.onConfigurationChange()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
new file mode 100644
index 0000000..d36a81b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+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
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QuickQuickSettingsViewModelTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply {
+            iconTilesRepository =
+                object : IconTilesRepository {
+                    override fun isIconTile(spec: TileSpec): Boolean {
+                        return spec.spec.startsWith(PREFIX_SMALL)
+                    }
+                }
+        }
+
+    private val underTest = kosmos.quickQuickSettingsViewModel
+
+    private val tiles =
+        listOf(
+                "$PREFIX_SMALL:1",
+                "$PREFIX_SMALL:2",
+                "$PREFIX_LARGE:3",
+                "$PREFIX_SMALL:4",
+                "$PREFIX_LARGE:5",
+                "$PREFIX_LARGE:6",
+                "$PREFIX_SMALL:7",
+                "$PREFIX_SMALL:8",
+                "$PREFIX_LARGE:9",
+            )
+            .map(TileSpec::create)
+
+    @Before
+    fun setUp() {
+        kosmos.setTiles(tiles)
+    }
+
+    @Test
+    fun splitIntoRows_onlyFirstTwoRowsOfTiles() =
+        with(kosmos) {
+            testScope.runTest {
+                setRows(2)
+                val columns by collectLastValue(underTest.columns)
+                val tileViewModels by collectLastValue(underTest.tileViewModels)
+
+                assertThat(columns).isEqualTo(4)
+                // All tiles in 4 columns
+                // [1] [2] [3 3]
+                // [4] [5 5]
+                // [6 6] [7] [8]
+                // [9 9]
+
+                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(5))
+            }
+        }
+
+    @Test
+    fun changeRows_tilesChange() =
+        with(kosmos) {
+            testScope.runTest {
+                setRows(2)
+                val columns by collectLastValue(underTest.columns)
+                val tileViewModels by collectLastValue(underTest.tileViewModels)
+
+                assertThat(columns).isEqualTo(4)
+                // All tiles in 4 columns
+                // [1] [2] [3 3]
+                // [4] [5 5]
+                // [6 6] [7] [8]
+                // [9 9]
+
+                setRows(3)
+                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(8))
+                setRows(1)
+                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(3))
+            }
+        }
+
+    @Test
+    fun changeTiles_tilesChange() =
+        with(kosmos) {
+            testScope.runTest {
+                setRows(2)
+                val columns by collectLastValue(underTest.columns)
+                val tileViewModels by collectLastValue(underTest.tileViewModels)
+
+                assertThat(columns).isEqualTo(4)
+                // All tiles in 4 columns
+                // [1] [2] [3 3]
+                // [4] [5 5]
+                // [6 6] [7] [8]
+                // [9 9]
+
+                // Remove tile small:4
+                currentTilesInteractor.removeTiles(setOf(tiles[3]))
+
+                assertThat(tileViewModels!!.map { it.tile.spec })
+                    .isEqualTo(
+                        listOf(
+                                "$PREFIX_SMALL:1",
+                                "$PREFIX_SMALL:2",
+                                "$PREFIX_LARGE:3",
+                                "$PREFIX_LARGE:5",
+                                "$PREFIX_LARGE:6",
+                            )
+                            .map(TileSpec::create)
+                    )
+            }
+        }
+
+    private fun Kosmos.setTiles(tiles: List<TileSpec>) {
+        currentTilesInteractor.setTiles(tiles)
+    }
+
+    private fun Kosmos.setRows(rows: Int) {
+        testCase.context.orCreateTestableResources.addOverride(
+            R.integer.quick_qs_panel_max_rows,
+            rows,
+        )
+        fakeConfigurationRepository.onConfigurationChange()
+    }
+
+    private companion object {
+        const val PREFIX_SMALL = "small"
+        const val PREFIX_LARGE = "large"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
index 411a7a4..a7a3a0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
@@ -19,6 +19,7 @@
 import android.testing.TestableLooper
 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.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -31,12 +32,12 @@
 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.qs.panels.ui.viewmodel.editModeViewModel
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -155,6 +156,24 @@
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
+    @Test
+    fun backTransitionSceneKey_notEditing_Home() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+
+            assertThat(destinationScenes?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
+        }
+
+    @Test
+    fun backTransition_editing_noDestination() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            kosmos.editModeViewModel.startEditing()
+
+            assertThat(destinationScenes!!).isNotEmpty()
+            assertThat(destinationScenes?.get(Back)).isNull()
+        }
+
     private fun TestScope.lockDevice() {
         val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index e73cae2..355669b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -38,8 +38,11 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setTransition
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -392,11 +395,15 @@
             )
             assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true)
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                testScope = testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition =
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GONE,
+                    )
             )
+
             assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
         }
 }
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index fd06238..14751ef 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -31,7 +31,6 @@
         android:id="@*android:id/contentPanel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="48dp"
         android:paddingStart="@dimen/dialog_side_padding"
         android:paddingEnd="@dimen/dialog_side_padding"
     >
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
index 046aecd..acefaae 100644
--- a/packages/SystemUI/res/layout/back.xml
+++ b/packages/SystemUI/res/layout/back.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/back"
diff --git a/packages/SystemUI/res/layout/contextual.xml b/packages/SystemUI/res/layout/contextual.xml
index 2cd7926..ac840d8 100644
--- a/packages/SystemUI/res/layout/contextual.xml
+++ b/packages/SystemUI/res/layout/contextual.xml
@@ -24,7 +24,7 @@
              android:clipChildren="false"
              android:clipToPadding="false"
              >
-    <com.android.systemui.navigationbar.buttons.KeyButtonView
+    <com.android.systemui.navigationbar.views.buttons.KeyButtonView
         android:id="@+id/menu"
         android:layout_height="match_parent"
         android:layout_width="match_parent"
@@ -47,7 +47,7 @@
              android:layout_height="match_parent"
              android:visibility="invisible"
     />
-    <com.android.systemui.navigationbar.buttons.KeyButtonView
+    <com.android.systemui.navigationbar.views.buttons.KeyButtonView
         android:id="@+id/accessibility_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/custom_key.xml b/packages/SystemUI/res/layout/custom_key.xml
index dc65777..0b0f4fa 100644
--- a/packages/SystemUI/res/layout/custom_key.xml
+++ b/packages/SystemUI/res/layout/custom_key.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="@dimen/navigation_side_padding"
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 84eed6a..227e390 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/home"
diff --git a/packages/SystemUI/res/layout/ime_switcher.xml b/packages/SystemUI/res/layout/ime_switcher.xml
index a2c8308..fa9a12b 100644
--- a/packages/SystemUI/res/layout/ime_switcher.xml
+++ b/packages/SystemUI/res/layout/ime_switcher.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/ime_switcher"
     android:layout_width="@dimen/navigation_key_width"
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index 0bb622b..443e4e3 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -25,7 +25,7 @@
     are placed inside a view that has a size controlled by weight. Ensure weight is large enough to
     support icon size. Use layout_width=navigation_side_padding like other navbar buttons. -->
 
-    <com.android.systemui.navigationbar.buttons.KeyButtonView
+    <com.android.systemui.navigationbar.views.buttons.KeyButtonView
         android:id="@+id/menu"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -43,7 +43,7 @@
         android:paddingStart="0dp"
         android:paddingEnd="0dp"
         />
-    <com.android.systemui.navigationbar.buttons.KeyButtonView
+    <com.android.systemui.navigationbar.views.buttons.KeyButtonView
         android:id="@+id/rotate_suggestion"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -51,7 +51,7 @@
         android:scaleType="centerInside"
         android:contentDescription="@string/accessibility_rotate_button"
         />
-    <com.android.systemui.navigationbar.buttons.KeyButtonView
+    <com.android.systemui.navigationbar.views.buttons.KeyButtonView
         android:id="@+id/accessibility_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index 5f59e78..a868ec4 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -17,7 +17,7 @@
 */
 -->
 
-<com.android.systemui.navigationbar.NavigationBarView
+<com.android.systemui.navigationbar.views.NavigationBarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/navigation_bar_view"
     android:layout_height="match_parent"
@@ -26,11 +26,11 @@
     android:clipToPadding="false"
     android:background="@drawable/system_bar_background">
 
-    <com.android.systemui.navigationbar.NavigationBarInflaterView
+    <com.android.systemui.navigationbar.views.NavigationBarInflaterView
         android:id="@+id/navigation_inflater"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipChildren="false"
         android:clipToPadding="false" />
 
-</com.android.systemui.navigationbar.NavigationBarView>
+</com.android.systemui.navigationbar.views.NavigationBarView>
diff --git a/packages/SystemUI/res/layout/navigation_bar_window.xml b/packages/SystemUI/res/layout/navigation_bar_window.xml
index b2473cd..9442124 100644
--- a/packages/SystemUI/res/layout/navigation_bar_window.xml
+++ b/packages/SystemUI/res/layout/navigation_bar_window.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-<com.android.systemui.navigationbar.NavigationBarFrame
+<com.android.systemui.navigationbar.views.NavigationBarFrame
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/navigation_bar_frame"
@@ -24,4 +24,4 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
-</com.android.systemui.navigationbar.NavigationBarFrame>
+</com.android.systemui.navigationbar.views.NavigationBarFrame>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index 46f238c..5c6c9a4 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -27,7 +27,7 @@
     android:clipToPadding="false"
     android:id="@+id/horizontal">
 
-    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
+    <com.android.systemui.navigationbar.views.buttons.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -53,6 +53,6 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
+    </com.android.systemui.navigationbar.views.buttons.NearestTouchFrame>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_vertical.xml b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
index 42e9324..c64e3f7 100644
--- a/packages/SystemUI/res/layout/navigation_layout_vertical.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
@@ -25,7 +25,7 @@
     android:paddingBottom="@dimen/nav_content_padding"
     android:id="@+id/vertical">
 
-    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
+    <com.android.systemui.navigationbar.views.buttons.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -33,7 +33,7 @@
         android:clipToPadding="false"
         systemui:isVertical="true">
 
-        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
+        <com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout
             android:id="@+id/ends_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -41,7 +41,7 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
+        <com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout
             android:id="@+id/center_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -50,6 +50,6 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
+    </com.android.systemui.navigationbar.views.buttons.NearestTouchFrame>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
index e2b1374..767e16c 100644
--- a/packages/SystemUI/res/layout/recent_apps.xml
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/recent_apps"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 177ba598..212dae2 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -233,6 +233,7 @@
     <item type="id" name="smart_space_barrier_bottom" />
     <item type="id" name="small_clock_guideline_top" />
     <item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
+    <item type="id" name="weather_clock_bc_smartspace_bottom" />
     <item type="id" name="accessibility_actions_view" />
 
     <!-- Privacy dialog -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 4af366c..0f11717 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -39,7 +39,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index 0cdb376..7dfa4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -31,9 +31,9 @@
 import android.view.View;
 
 import com.android.settingslib.Utils;
-import com.android.systemui.navigationbar.NavigationBar;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarTransitions;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarTransitions;
 import com.android.systemui.res.R;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ad142a8..9d3c6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -558,7 +558,12 @@
                 Log.w(TAG, "onTouch down received without a preceding up");
             }
             mActivePointerId = MotionEvent.INVALID_POINTER_ID;
-            mOnFingerDown = false;
+
+            // It's possible on some devices to get duplicate touches from both doze and the
+            // normal touch listener. Don't reset the down in this case to avoid duplicate downs
+            if (!mIsAodInterruptActive) {
+                mOnFingerDown = false;
+            }
         } else if (!DeviceEntryUdfpsRefactor.isEnabled()) {
             if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
                     && !mAlternateBouncerInteractor.isVisibleState())
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e31f1ad..88c3f9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -18,7 +18,6 @@
 
 import android.provider.Settings
 import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -92,8 +91,8 @@
         keyguardTransitionInteractor.startedKeyguardTransitionStep
             .mapLatest(::determineSceneAfterTransition)
             .filterNotNull()
-            .onEach { (nextScene, nextTransition) ->
-                communalSceneInteractor.changeScene(nextScene, nextTransition)
+            .onEach { nextScene ->
+                communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
             }
             .launchIn(applicationScope)
 
@@ -189,7 +188,7 @@
 
     private suspend fun determineSceneAfterTransition(
         lastStartedTransition: TransitionStep,
-    ): Pair<SceneKey, TransitionKey>? {
+    ): SceneKey? {
         val to = lastStartedTransition.to
         val from = lastStartedTransition.from
         val docked = dockManager.isDocked
@@ -202,27 +201,22 @@
                 // underneath the hub is shown. When launching activities over lockscreen, we only
                 // change scenes once the activity launch animation is finished, so avoid
                 // changing the scene here.
-                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+                CommunalScenes.Blank
             }
             to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
                 // When transitioning to the hub from an occluded state, fade out the hub without
                 // doing any translation.
-                Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
+                CommunalScenes.Communal
             }
             // Transitioning to Blank scene when entering the edit mode will be handled separately
             // with custom animations.
             to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
-                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+                CommunalScenes.Blank
             !docked && !KeyguardState.deviceIsAwakeInState(to) -> {
                 // If the user taps the screen and wakes the device within this timeout, we don't
                 // want to dismiss the hub
                 delay(AWAKE_DEBOUNCE_DELAY)
-                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
-            }
-            from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
-                // Make sure the communal hub is showing (immediately, not fading in) when
-                // transitioning from dozing to hub.
-                Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+                CommunalScenes.Blank
             }
             else -> null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 2940a95..e3ef6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -115,11 +115,11 @@
                 val intType =
                     secureSettings.getIntForUser(
                         GLANCEABLE_HUB_BACKGROUND_SETTING,
-                        CommunalBackgroundType.DEFAULT.value,
+                        CommunalBackgroundType.ANIMATED.value,
                         user.id
                     )
                 CommunalBackgroundType.entries.find { type -> type.value == intType }
-                    ?: CommunalBackgroundType.DEFAULT
+                    ?: CommunalBackgroundType.ANIMATED
             }
 
     private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
index 4eaba06..3b23ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
@@ -18,7 +18,7 @@
 
 /** Models the types of background that can be shown on the hub. */
 enum class CommunalBackgroundType(val value: Int) {
-    DEFAULT(0),
+    STATIC(0),
     STATIC_GRADIENT(1),
     ANIMATED(2),
     NONE(3),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index fab2435..2e92438 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -93,7 +93,7 @@
      */
     val canShowEditMode =
         allOf(
-                keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+                keyguardTransitionInteractor.isFinishedIn(KeyguardState.GONE),
                 communalInteractor.editModeOpen
             )
             .filter { it }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 0466bbc..f9f01f7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
@@ -144,7 +145,10 @@
      */
     override val isCommunalContentFlowFrozen: Flow<Boolean> =
         allOf(
-                keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+                keyguardTransitionInteractor.isFinishedIn(
+                    scene = Scenes.Communal,
+                    stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB
+                ),
                 keyguardInteractor.isKeyguardOccluded,
                 not(keyguardInteractor.isAbleToDream)
             )
@@ -177,7 +181,10 @@
     // opened.
     override val isFocusable: Flow<Boolean> =
         combine(
-                keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+                keyguardTransitionInteractor.isFinishedIn(
+                    scene = Scenes.Communal,
+                    stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB
+                ),
                 communalInteractor.isIdleOnCommunal,
                 shadeInteractor.isAnyFullyExpanded,
             ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
index 1e04fe7..4217744 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
@@ -56,7 +56,7 @@
         Color.valueOf(
             Utils.getColorAttrDefaultColor(
                 context,
-                com.android.internal.R.attr.materialColorOutlineVariant
+                com.android.internal.R.attr.materialColorPrimary
             )
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 813fccf..17202639 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
@@ -221,8 +222,7 @@
                 trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged")
                 awaitClose { it.unregisterOnBypassStateChangedListener(callback) }
             }
-        }
-            ?: flowOf(false)
+        } ?: flowOf(false)
 
     override fun setLockedOut(isLockedOut: Boolean) {
         _isLockedOut.value = isLockedOut
@@ -322,7 +322,10 @@
         merge(
                 powerInteractor.isAsleep,
                 combine(
-                    keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+                    keyguardTransitionInteractor.isFinishedIn(
+                        scene = Scenes.Gone,
+                        stateWithoutSceneContainer = KeyguardState.GONE
+                    ),
                     keyguardInteractor.statusBarState,
                 ) { isFinishedInGoneState, statusBarState ->
                     // When the user is dragging the primary bouncer in (up) by manually scrolling
@@ -733,6 +736,7 @@
         pw.println("  lockscreenBypassEnabled: ${keyguardBypassController?.bypassEnabled ?: false}")
     }
 }
+
 /** Combine two boolean flows by and-ing both of them */
 private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
     flow.combine(anotherFlow) { a, b -> a && b }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 245def8..4fd1c24 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -53,6 +53,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.BlurUtils;
@@ -274,13 +275,19 @@
             collectFlow(
                     mView,
                     FlowKt.distinctUntilChanged(combineFlows(
-                            mKeyguardTransitionInteractor.isFinishedInStateWhere(
-                                    KeyguardState::isBouncerState),
+                            mKeyguardTransitionInteractor.isFinishedIn(
+                                    Scenes.Bouncer, KeyguardState.PRIMARY_BOUNCER),
+                            mKeyguardTransitionInteractor.isFinishedIn(
+                                    KeyguardState.ALTERNATE_BOUNCER),
                             mShadeInteractor.isAnyExpanded(),
                             mCommunalInteractor.isCommunalShowing(),
-                            (anyBouncerShowing, shadeExpanded, communalShowing) -> {
-                                mAnyBouncerShowing = anyBouncerShowing;
-                                return anyBouncerShowing || shadeExpanded || communalShowing;
+                            (primaryBouncerShowing,
+                                    alternateBouncerShowing,
+                                    shadeExpanded,
+                                    communalShowing) -> {
+                                mAnyBouncerShowing = primaryBouncerShowing
+                                        || alternateBouncerShowing;
+                                return mAnyBouncerShowing || shadeExpanded || communalShowing;
                             })),
                     mDreamManager::setDreamIsObscured,
                     mBackgroundDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
new file mode 100644
index 0000000..0aced8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -0,0 +1,351 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.view.KeyEvent.KEYCODE_ALT_LEFT
+import android.view.KeyEvent.KEYCODE_ALT_RIGHT
+import android.view.KeyEvent.KEYCODE_BACK
+import android.view.KeyEvent.KEYCODE_BREAK
+import android.view.KeyEvent.KEYCODE_BUTTON_A
+import android.view.KeyEvent.KEYCODE_BUTTON_B
+import android.view.KeyEvent.KEYCODE_BUTTON_C
+import android.view.KeyEvent.KEYCODE_BUTTON_L1
+import android.view.KeyEvent.KEYCODE_BUTTON_L2
+import android.view.KeyEvent.KEYCODE_BUTTON_MODE
+import android.view.KeyEvent.KEYCODE_BUTTON_R1
+import android.view.KeyEvent.KEYCODE_BUTTON_R2
+import android.view.KeyEvent.KEYCODE_BUTTON_SELECT
+import android.view.KeyEvent.KEYCODE_BUTTON_START
+import android.view.KeyEvent.KEYCODE_BUTTON_X
+import android.view.KeyEvent.KEYCODE_BUTTON_Y
+import android.view.KeyEvent.KEYCODE_BUTTON_Z
+import android.view.KeyEvent.KEYCODE_CTRL_LEFT
+import android.view.KeyEvent.KEYCODE_CTRL_RIGHT
+import android.view.KeyEvent.KEYCODE_DEL
+import android.view.KeyEvent.KEYCODE_DPAD_CENTER
+import android.view.KeyEvent.KEYCODE_DPAD_DOWN
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
+import android.view.KeyEvent.KEYCODE_DPAD_UP
+import android.view.KeyEvent.KEYCODE_EISU
+import android.view.KeyEvent.KEYCODE_ENTER
+import android.view.KeyEvent.KEYCODE_EQUALS
+import android.view.KeyEvent.KEYCODE_ESCAPE
+import android.view.KeyEvent.KEYCODE_F1
+import android.view.KeyEvent.KEYCODE_F10
+import android.view.KeyEvent.KEYCODE_F11
+import android.view.KeyEvent.KEYCODE_F12
+import android.view.KeyEvent.KEYCODE_F2
+import android.view.KeyEvent.KEYCODE_F3
+import android.view.KeyEvent.KEYCODE_F4
+import android.view.KeyEvent.KEYCODE_F5
+import android.view.KeyEvent.KEYCODE_F6
+import android.view.KeyEvent.KEYCODE_F7
+import android.view.KeyEvent.KEYCODE_F8
+import android.view.KeyEvent.KEYCODE_F9
+import android.view.KeyEvent.KEYCODE_FORWARD_DEL
+import android.view.KeyEvent.KEYCODE_GRAVE
+import android.view.KeyEvent.KEYCODE_HENKAN
+import android.view.KeyEvent.KEYCODE_HOME
+import android.view.KeyEvent.KEYCODE_INSERT
+import android.view.KeyEvent.KEYCODE_KATAKANA_HIRAGANA
+import android.view.KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
+import android.view.KeyEvent.KEYCODE_MEDIA_NEXT
+import android.view.KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+import android.view.KeyEvent.KEYCODE_MEDIA_PREVIOUS
+import android.view.KeyEvent.KEYCODE_MEDIA_REWIND
+import android.view.KeyEvent.KEYCODE_MEDIA_STOP
+import android.view.KeyEvent.KEYCODE_MINUS
+import android.view.KeyEvent.KEYCODE_MOVE_END
+import android.view.KeyEvent.KEYCODE_MOVE_HOME
+import android.view.KeyEvent.KEYCODE_MUHENKAN
+import android.view.KeyEvent.KEYCODE_NUMPAD_0
+import android.view.KeyEvent.KEYCODE_NUMPAD_1
+import android.view.KeyEvent.KEYCODE_NUMPAD_2
+import android.view.KeyEvent.KEYCODE_NUMPAD_3
+import android.view.KeyEvent.KEYCODE_NUMPAD_4
+import android.view.KeyEvent.KEYCODE_NUMPAD_5
+import android.view.KeyEvent.KEYCODE_NUMPAD_6
+import android.view.KeyEvent.KEYCODE_NUMPAD_7
+import android.view.KeyEvent.KEYCODE_NUMPAD_8
+import android.view.KeyEvent.KEYCODE_NUMPAD_9
+import android.view.KeyEvent.KEYCODE_NUMPAD_ADD
+import android.view.KeyEvent.KEYCODE_NUMPAD_COMMA
+import android.view.KeyEvent.KEYCODE_NUMPAD_DIVIDE
+import android.view.KeyEvent.KEYCODE_NUMPAD_DOT
+import android.view.KeyEvent.KEYCODE_NUMPAD_ENTER
+import android.view.KeyEvent.KEYCODE_NUMPAD_EQUALS
+import android.view.KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN
+import android.view.KeyEvent.KEYCODE_NUMPAD_MULTIPLY
+import android.view.KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN
+import android.view.KeyEvent.KEYCODE_NUMPAD_SUBTRACT
+import android.view.KeyEvent.KEYCODE_NUM_LOCK
+import android.view.KeyEvent.KEYCODE_PAGE_DOWN
+import android.view.KeyEvent.KEYCODE_PAGE_UP
+import android.view.KeyEvent.KEYCODE_PERIOD
+import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
+import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
+import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
+import android.view.KeyEvent.KEYCODE_SPACE
+import android.view.KeyEvent.KEYCODE_SYSRQ
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.KEYCODE_ZENKAKU_HANKAKU
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import android.view.KeyEvent.META_SYM_ON
+import com.android.systemui.res.R
+
+object ShortcutHelperKeys {
+
+    val keyIcons =
+        mapOf(
+            META_META_ON to R.drawable.ic_ksh_key_meta,
+        )
+
+    val specialKeyLabels =
+        mapOf<Int, (Context) -> String>(
+            KEYCODE_HOME to { context -> context.getString(R.string.keyboard_key_home) },
+            KEYCODE_BACK to { context -> context.getString(R.string.keyboard_key_back) },
+            KEYCODE_DPAD_UP to { context -> context.getString(R.string.keyboard_key_dpad_up) },
+            KEYCODE_DPAD_DOWN to { context -> context.getString(R.string.keyboard_key_dpad_down) },
+            KEYCODE_DPAD_LEFT to { context -> context.getString(R.string.keyboard_key_dpad_left) },
+            KEYCODE_DPAD_RIGHT to
+                { context ->
+                    context.getString(R.string.keyboard_key_dpad_right)
+                },
+            KEYCODE_DPAD_CENTER to
+                { context ->
+                    context.getString(R.string.keyboard_key_dpad_center)
+                },
+            KEYCODE_PERIOD to { "." },
+            KEYCODE_TAB to { context -> context.getString(R.string.keyboard_key_tab) },
+            KEYCODE_SPACE to { context -> context.getString(R.string.keyboard_key_space) },
+            KEYCODE_ENTER to { context -> context.getString(R.string.keyboard_key_enter) },
+            KEYCODE_DEL to { context -> context.getString(R.string.keyboard_key_backspace) },
+            KEYCODE_MEDIA_PLAY_PAUSE to
+                { context ->
+                    context.getString(R.string.keyboard_key_media_play_pause)
+                },
+            KEYCODE_MEDIA_STOP to
+                { context ->
+                    context.getString(R.string.keyboard_key_media_stop)
+                },
+            KEYCODE_MEDIA_NEXT to
+                { context ->
+                    context.getString(R.string.keyboard_key_media_next)
+                },
+            KEYCODE_MEDIA_PREVIOUS to
+                { context ->
+                    context.getString(R.string.keyboard_key_media_previous)
+                },
+            KEYCODE_MEDIA_REWIND to
+                { context ->
+                    context.getString(R.string.keyboard_key_media_rewind)
+                },
+            KEYCODE_MEDIA_FAST_FORWARD to
+                { context ->
+                    context.getString(R.string.keyboard_key_media_fast_forward)
+                },
+            KEYCODE_PAGE_UP to { context -> context.getString(R.string.keyboard_key_page_up) },
+            KEYCODE_PAGE_DOWN to { context -> context.getString(R.string.keyboard_key_page_down) },
+            KEYCODE_BUTTON_A to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "A")
+                },
+            KEYCODE_BUTTON_B to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "B")
+                },
+            KEYCODE_BUTTON_C to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "C")
+                },
+            KEYCODE_BUTTON_X to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "X")
+                },
+            KEYCODE_BUTTON_Y to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "Y")
+                },
+            KEYCODE_BUTTON_Z to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "Z")
+                },
+            KEYCODE_BUTTON_L1 to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "L1")
+                },
+            KEYCODE_BUTTON_R1 to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "R1")
+                },
+            KEYCODE_BUTTON_L2 to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "L2")
+                },
+            KEYCODE_BUTTON_R2 to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "R2")
+                },
+            KEYCODE_BUTTON_START to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "Start")
+                },
+            KEYCODE_BUTTON_SELECT to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "Select")
+                },
+            KEYCODE_BUTTON_MODE to
+                { context ->
+                    context.getString(R.string.keyboard_key_button_template, "Mode")
+                },
+            KEYCODE_FORWARD_DEL to
+                { context ->
+                    context.getString(R.string.keyboard_key_forward_del)
+                },
+            KEYCODE_ESCAPE to { "Esc" },
+            KEYCODE_SYSRQ to { "SysRq" },
+            KEYCODE_BREAK to { "Break" },
+            KEYCODE_SCROLL_LOCK to { "Scroll Lock" },
+            KEYCODE_MOVE_HOME to { context -> context.getString(R.string.keyboard_key_move_home) },
+            KEYCODE_MOVE_END to { context -> context.getString(R.string.keyboard_key_move_end) },
+            KEYCODE_INSERT to { context -> context.getString(R.string.keyboard_key_insert) },
+            KEYCODE_F1 to { "F1" },
+            KEYCODE_F2 to { "F2" },
+            KEYCODE_F3 to { "F3" },
+            KEYCODE_F4 to { "F4" },
+            KEYCODE_F5 to { "F5" },
+            KEYCODE_F6 to { "F6" },
+            KEYCODE_F7 to { "F7" },
+            KEYCODE_F8 to { "F8" },
+            KEYCODE_F9 to { "F9" },
+            KEYCODE_F10 to { "F10" },
+            KEYCODE_F11 to { "F11" },
+            KEYCODE_F12 to { "F12" },
+            KEYCODE_NUM_LOCK to { context -> context.getString(R.string.keyboard_key_num_lock) },
+            KEYCODE_MINUS to { "-" },
+            KEYCODE_GRAVE to { "`" },
+            KEYCODE_EQUALS to { "=" },
+            KEYCODE_NUMPAD_0 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "0")
+                },
+            KEYCODE_NUMPAD_1 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "1")
+                },
+            KEYCODE_NUMPAD_2 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "2")
+                },
+            KEYCODE_NUMPAD_3 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "3")
+                },
+            KEYCODE_NUMPAD_4 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "4")
+                },
+            KEYCODE_NUMPAD_5 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "5")
+                },
+            KEYCODE_NUMPAD_6 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "6")
+                },
+            KEYCODE_NUMPAD_7 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "7")
+                },
+            KEYCODE_NUMPAD_8 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "8")
+                },
+            KEYCODE_NUMPAD_9 to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "9")
+                },
+            KEYCODE_NUMPAD_DIVIDE to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "/")
+                },
+            KEYCODE_NUMPAD_MULTIPLY to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "*")
+                },
+            KEYCODE_NUMPAD_SUBTRACT to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "-")
+                },
+            KEYCODE_NUMPAD_ADD to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "+")
+                },
+            KEYCODE_NUMPAD_DOT to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, ".")
+                },
+            KEYCODE_NUMPAD_COMMA to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, ",")
+                },
+            KEYCODE_NUMPAD_ENTER to
+                { context ->
+                    context.getString(
+                        R.string.keyboard_key_numpad_template,
+                        context.getString(R.string.keyboard_key_enter)
+                    )
+                },
+            KEYCODE_NUMPAD_EQUALS to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "=")
+                },
+            KEYCODE_NUMPAD_LEFT_PAREN to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, "(")
+                },
+            KEYCODE_NUMPAD_RIGHT_PAREN to
+                { context ->
+                    context.getString(R.string.keyboard_key_numpad_template, ")")
+                },
+            KEYCODE_ZENKAKU_HANKAKU to { "半角/全角" },
+            KEYCODE_EISU to { "英数" },
+            KEYCODE_MUHENKAN to { "無変換" },
+            KEYCODE_HENKAN to { "変換" },
+            KEYCODE_KATAKANA_HIRAGANA to { "かな" },
+            KEYCODE_ALT_LEFT to { "Alt" },
+            KEYCODE_ALT_RIGHT to { "Alt" },
+            KEYCODE_CTRL_LEFT to { "Ctrl" },
+            KEYCODE_CTRL_RIGHT to { "Ctrl" },
+            KEYCODE_SHIFT_LEFT to { "Shift" },
+            KEYCODE_SHIFT_RIGHT to { "Shift" },
+
+            // Modifiers
+            META_META_ON to { "Meta" },
+            META_CTRL_ON to { "Ctrl" },
+            META_ALT_ON to { "Alt" },
+            META_SHIFT_ON to { "Shift" },
+            META_SYM_ON to { "Sym" },
+            META_FUNCTION_ON to { "Fn" },
+        )
+}
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 506a18d..b423ed9 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
@@ -17,10 +17,8 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -53,10 +51,8 @@
     keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
-    private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     val deviceEntryRepository: DeviceEntryRepository,
-    private val dreamManager: DreamManager,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
@@ -123,8 +119,7 @@
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
                     keyguardInteractor.isKeyguardOccluded,
-                    communalInteractor.isCommunalAvailable,
-                    communalSceneInteractor.isIdleOnCommunal,
+                    communalInteractor.isIdleOnCommunal,
                     canTransitionToGoneOnWake,
                     keyguardInteractor.primaryBouncerShowing,
                 )
@@ -132,7 +127,6 @@
                     (
                         _,
                         occluded,
-                        isCommunalAvailable,
                         isIdleOnCommunal,
                         canTransitionToGoneOnWake,
                         primaryBouncerShowing) ->
@@ -147,10 +141,6 @@
                             KeyguardState.OCCLUDED
                         } else if (isIdleOnCommunal) {
                             KeyguardState.GLANCEABLE_HUB
-                        } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
-                            // This case handles tapping the power button to transition through
-                            // dream -> off -> hub.
-                            KeyguardState.GLANCEABLE_HUB
                         } else {
                             KeyguardState.LOCKSCREEN
                         }
@@ -169,8 +159,7 @@
             powerInteractor.detailedWakefulness
                 .filterRelevantKeyguardStateAnd { it.isAwake() }
                 .sample(
-                    communalInteractor.isCommunalAvailable,
-                    communalSceneInteractor.isIdleOnCommunal,
+                    communalInteractor.isIdleOnCommunal,
                     keyguardInteractor.biometricUnlockState,
                     canTransitionToGoneOnWake,
                     keyguardInteractor.primaryBouncerShowing,
@@ -178,7 +167,6 @@
                 .collect {
                     (
                         _,
-                        isCommunalAvailable,
                         isIdleOnCommunal,
                         biometricUnlockState,
                         canDismissLockscreen,
@@ -200,10 +188,6 @@
                                 KeyguardState.PRIMARY_BOUNCER
                             } else if (isIdleOnCommunal) {
                                 KeyguardState.GLANCEABLE_HUB
-                            } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
-                                // This case handles tapping the power button to transition through
-                                // dream -> off -> hub.
-                                KeyguardState.GLANCEABLE_HUB
                             } else {
                                 KeyguardState.LOCKSCREEN
                             },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 37272dc..2d389aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -530,17 +530,23 @@
         return finishedKeyguardState.map { stateMatcher(it) }.distinctUntilChanged()
     }
 
-    /** Whether we've FINISHED a transition to a state that matches the given predicate. */
-    fun isFinishedInState(state: KeyguardState): Flow<Boolean> {
-        return finishedKeyguardState.map { it == state }.distinctUntilChanged()
+    fun isFinishedIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
+        return if (SceneContainerFlag.isEnabled) {
+            sceneInteractor
+                .get()
+                .transitionState
+                .map { it.isIdle(scene) || it.isTransitioning(from = scene) }
+                .distinctUntilChanged()
+        } else {
+            isFinishedIn(stateWithoutSceneContainer)
+        }
     }
 
-    /**
-     * Whether we've FINISHED a transition to a state that matches the given predicate. Consider
-     * using [isFinishedInStateWhere] whenever possible instead
-     */
-    fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
-        stateMatcher(finishedKeyguardState.replayCache.last())
+    /** Whether we've FINISHED a transition to a state */
+    fun isFinishedIn(state: KeyguardState): Flow<Boolean> {
+        state.checkValidState()
+        return finishedKeyguardState.map { it == state }.distinctUntilChanged()
+    }
 
     fun getCurrentState(): KeyguardState {
         return currentKeyguardState.replayCache.last()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 6a2bb5f..8db6a43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -15,7 +15,9 @@
  */
 package com.android.systemui.keyguard.shared.model
 
+import android.util.Log
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 
 /** List of all possible states to transition to/from */
@@ -87,6 +89,22 @@
     /** An activity is displaying over the keyguard. */
     OCCLUDED;
 
+    fun checkValidState() {
+        val isStateValid: Boolean
+        val isEnabled: String
+        if (SceneContainerFlag.isEnabled) {
+            isStateValid = this === mapToSceneContainerState()
+            isEnabled = "enabled"
+        } else {
+            isStateValid = this !== UNDEFINED
+            isEnabled = "disabled"
+        }
+
+        if (!isStateValid) {
+            Log.e("KeyguardState", "$this is not a valid state when scene container is $isEnabled")
+        }
+    }
+
     fun mapToSceneContainerState(): KeyguardState {
         return when (this) {
             OFF,
@@ -128,17 +146,12 @@
             return state != GONE
         }
 
-        /** Whether either of the bouncers are visible when we're FINISHED in the given state. */
-        @JvmStatic
-        fun isBouncerState(state: KeyguardState): Boolean {
-            return state == PRIMARY_BOUNCER || state == ALTERNATE_BOUNCER
-        }
-
         /**
          * Whether the device is awake ([PowerInteractor.isAwake]) when we're FINISHED in the given
          * keyguard state.
          */
         fun deviceIsAwakeInState(state: KeyguardState): Boolean {
+            state.checkValidState()
             return when (state) {
                 OFF -> false
                 DOZING -> false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index f2821a0..e063380 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -37,6 +37,10 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.AodClockBurnInModel
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.util.ui.value
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
 object KeyguardClockViewBinder {
@@ -99,15 +103,20 @@
 
                 launch {
                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    viewModel.isAodIconsVisible.collect {
-                        viewModel.currentClock.value?.let {
-                            if (
-                                viewModel.isLargeClockVisible.value && it.config.useCustomClockScene
-                            ) {
-                                blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+                    combine(
+                            viewModel.hasAodIcons,
+                            rootViewModel.isNotifIconContainerVisible.map { it.value }
+                        ) { hasIcon, isVisible ->
+                            hasIcon && isVisible
+                        }
+                        .distinctUntilChanged()
+                        .collect { _ ->
+                            viewModel.currentClock.value?.let {
+                                if (it.config.useCustomClockScene) {
+                                    blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+                                }
                             }
                         }
-                    }
                 }
 
                 launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 34a1da5..0637bba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
+import com.android.systemui.util.ui.value
 import dagger.Lazy
 import javax.inject.Inject
 
@@ -70,6 +71,7 @@
     private val rootViewModel: KeyguardRootViewModel,
 ) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
+
     override fun bindData(constraintLayout: ConstraintLayout) {
         if (!MigrateClocksToBlueprint.isEnabled) {
             return
@@ -121,35 +123,39 @@
     private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
         if (keyguardClockViewModel.isLargeClockVisible.value) clock.largeClock.layout
         else clock.smallClock.layout
+
     private fun getNonTargetClockFace(clock: ClockController): ClockFaceLayout =
         if (keyguardClockViewModel.isLargeClockVisible.value) clock.smallClock.layout
         else clock.largeClock.layout
 
     fun constrainWeatherClockDateIconsBarrier(constraints: ConstraintSet) {
         constraints.apply {
-            if (keyguardClockViewModel.isAodIconsVisible.value) {
+            createBarrier(
+                R.id.weather_clock_bc_smartspace_bottom,
+                Barrier.BOTTOM,
+                getDimen(ENHANCED_SMARTSPACE_HEIGHT),
+                (custR.id.weather_clock_time)
+            )
+            if (
+                rootViewModel.isNotifIconContainerVisible.value.value &&
+                    keyguardClockViewModel.hasAodIcons.value
+            ) {
                 createBarrier(
                     R.id.weather_clock_date_and_icons_barrier_bottom,
                     Barrier.BOTTOM,
                     0,
-                    *intArrayOf(sharedR.id.bc_smartspace_view, R.id.aod_notification_icon_container)
+                    *intArrayOf(
+                        R.id.aod_notification_icon_container,
+                        R.id.weather_clock_bc_smartspace_bottom
+                    )
                 )
             } else {
-                if (smartspaceViewModel.bcSmartspaceVisibility.value == VISIBLE) {
-                    createBarrier(
-                        R.id.weather_clock_date_and_icons_barrier_bottom,
-                        Barrier.BOTTOM,
-                        0,
-                        (sharedR.id.bc_smartspace_view)
-                    )
-                } else {
-                    createBarrier(
-                        R.id.weather_clock_date_and_icons_barrier_bottom,
-                        Barrier.BOTTOM,
-                        getDimen(ENHANCED_SMARTSPACE_HEIGHT),
-                        (R.id.lockscreen_clock_view)
-                    )
-                }
+                createBarrier(
+                    R.id.weather_clock_date_and_icons_barrier_bottom,
+                    Barrier.BOTTOM,
+                    0,
+                    *intArrayOf(R.id.weather_clock_bc_smartspace_bottom)
+                )
             }
         }
     }
@@ -198,6 +204,7 @@
     companion object {
         private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
         private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+
         fun getDimen(context: Context, name: String): Int {
             val res = context.packageManager.getResourcesForApplication(context.packageName)
             val id = res.getIdentifier(name, "dimen", context.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
index 4128c52..5cf100e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
@@ -34,8 +34,8 @@
     alternateBouncerInteractor: AlternateBouncerInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
-    val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer
-
+    private val deviceSupportsAlternateBouncer: Flow<Boolean> =
+        alternateBouncerInteractor.alternateBouncerSupported
     private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> =
         keyguardTransitionInteractor
             .transitionValue(KeyguardState.ALTERNATE_BOUNCER)
@@ -43,8 +43,8 @@
             .distinctUntilChanged()
 
     val alternateBouncerWindowRequired: Flow<Boolean> =
-        canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer ->
-            if (canShowAlternateBouncer) {
+        deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer ->
+            if (deviceSupportsAlternateBouncer) {
                 isTransitioningToOrFromOrShowingAlternateBouncer
             } else {
                 flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f5c521a..573b75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
 import javax.inject.Inject
@@ -50,7 +49,6 @@
     keyguardClockInteractor: KeyguardClockInteractor,
     @Application private val applicationScope: CoroutineScope,
     aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-    notifsKeyguardInteractor: NotificationsKeyguardInteractor,
     @get:VisibleForTesting val shadeInteractor: ShadeInteractor,
     private val systemBarUtils: SystemBarUtilsProxy,
     configurationInteractor: ConfigurationInteractor,
@@ -90,14 +88,13 @@
                 currentClock?.let { clock ->
                     val face = if (isLargeClock) clock.largeClock else clock.smallClock
                     face.config.hasCustomWeatherDataDisplay
-                }
-                    ?: false
+                } ?: false
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay
-                        ?: false
+                initialValue =
+                    currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false
             )
 
     val clockShouldBeCentered: StateFlow<Boolean> =
@@ -109,15 +106,14 @@
 
     // To translate elements below smartspace in weather clock to avoid overlapping between date
     // element in weather clock and aod icons
-    val isAodIconsVisible: StateFlow<Boolean> = combine(aodNotificationIconViewModel.icons.map {
-        it.visibleIcons.isNotEmpty()
-    }, notifsKeyguardInteractor.areNotificationsFullyHidden) { hasIcons, visible ->
-        hasIcons && visible
-    }.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = false
-        )
+    val hasAodIcons: StateFlow<Boolean> =
+        aodNotificationIconViewModel.icons
+            .map { it.visibleIcons.isNotEmpty() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false
+            )
 
     val currentClockLayout: StateFlow<ClockLayout> =
         combine(
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 d7ac976..0fb29c2 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
@@ -143,7 +143,7 @@
 
     private val isOnLockscreen: Flow<Boolean> =
         combine(
-                keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) },
+                keyguardTransitionInteractor.isFinishedIn(LOCKSCREEN).onStart { emit(false) },
                 anyOf(
                     keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)),
                     keyguardTransitionInteractor.isInTransition(Edge.create(from = LOCKSCREEN)),
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
index 939dab2..28bdbb2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -20,13 +20,14 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.android.systemui.log.LogBuffer;
+import com.android.systemui.navigationbar.views.NavigationBar;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
 import javax.inject.Qualifier;
 
-/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+/** A {@link LogBuffer} for {@link NavigationBar}. */
 @Qualifier
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
index 46790a6..d09eeb2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
@@ -19,13 +19,14 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.android.systemui.log.LogBuffer;
+import com.android.systemui.navigationbar.views.NavigationBar;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
 import javax.inject.Qualifier;
 
-/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+/** A {@link LogBuffer} for {@link NavigationBar}. */
 @Qualifier
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index b393155..13a786a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -573,7 +573,7 @@
         }
     }
 
-    static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
+    public static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
         final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS;
         if (isTransient) {
             return MODE_SEMI_TRANSPARENT;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
index 4f713d6..abd8bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
@@ -24,6 +24,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.navigationbar.views.NavigationBar;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index c801662..49fa3ba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 
 /** A controller to handle navigation bars. */
 public interface NavigationBarController {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
index 06a78c5..c392c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
@@ -19,6 +19,8 @@
 import com.android.internal.statusbar.RegisterStatusBarResult
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shared.statusbar.phone.BarTransitions
+import com.android.systemui.navigationbar.views.NavigationBar
+import com.android.systemui.navigationbar.views.NavigationBarView
 import javax.inject.Inject
 
 /** A no-op version of [NavigationBarController] for variants like Arc and TV. */
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index f0207aa..1dbdec9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -54,6 +54,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index aab4fea..e2ba761 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,6 +24,8 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.views.NavigationBarFrame;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.res.R;
 
 import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
index d1ce1f6..1b985d20 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
@@ -32,7 +32,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
-import com.android.systemui.navigationbar.buttons.ButtonInterface;
+import com.android.systemui.navigationbar.views.buttons.ButtonInterface;
 import com.android.systemui.res.R;
 
 public class NavigationHandle extends View implements ButtonInterface {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 89dce03..e832abb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
@@ -117,11 +117,15 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
+import com.android.systemui.navigationbar.NavBarHelper;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.DeadZone;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1992,7 +1996,7 @@
         return mSamplingBounds;
     }
 
-    void setNavigationBarLumaSamplingEnabled(boolean enable) {
+    public void setNavigationBarLumaSamplingEnabled(boolean enable) {
         if (enable) {
             mRegionSamplingHelper.start(mSamplingBounds);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarFrame.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarFrame.java
index 6c531d8..3388f93 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarFrame.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static android.view.MotionEvent.ACTION_OUTSIDE;
 
@@ -26,7 +26,7 @@
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
-import com.android.systemui.navigationbar.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
 
 public class NavigationBarFrame extends FrameLayout {
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
index 2ae0709..96b730c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -36,10 +36,11 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
-import com.android.systemui.navigationbar.buttons.ReverseLinearLayout;
-import com.android.systemui.navigationbar.buttons.ReverseLinearLayout.ReverseRelativeLayout;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout;
+import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout.ReverseRelativeLayout;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarTransitions.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarTransitions.java
index 5442598..251745d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarTransitions.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
@@ -25,7 +25,7 @@
 import android.view.View;
 
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 2c6e297..0f36097 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -66,12 +66,13 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.ContextualButton;
-import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
-import com.android.systemui.navigationbar.buttons.DeadZone;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
+import com.android.systemui.navigationbar.ScreenPinningNotify;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.ContextualButton;
+import com.android.systemui.navigationbar.views.buttons.ContextualButtonGroup;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.views.buttons.NearestTouchFrame;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.res.R;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonDispatcher.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonDispatcher.java
index fc37b9f..6799a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonDispatcher.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import static com.android.app.animation.Interpolators.LINEAR;
 
@@ -25,8 +25,6 @@
 import android.view.View.AccessibilityDelegate;
 import android.view.ViewGroup;
 
-import com.android.systemui.navigationbar.NavBarButtonClickLogger;
-
 import java.util.ArrayList;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonInterface.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonInterface.java
index 5f8fafd..c1b4944 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonInterface.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButton.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButton.java
index 517f8e7..5e6bfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButton.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButtonGroup.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButtonGroup.java
index 2ace303..c68d25f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButtonGroup.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.annotation.IdRes;
 import android.annotation.NonNull;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/DeadZone.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/DeadZone.java
index 8177fde..a6be314 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/DeadZone.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.animation.ObjectAnimator;
 import android.content.res.Resources;
@@ -27,7 +27,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.res.R;
 
 import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java
index 686facc..072a4db 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.animation.ArgbEvaluator;
 import android.annotation.ColorInt;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index dbe87ea..1e85d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
@@ -59,7 +59,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.navigationbar.NavBarButtonClickLogger;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.navigationbar.KeyButtonRipple;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavBarButtonClickLogger.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavBarButtonClickLogger.kt
index 408acf3..ca58718 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavBarButtonClickLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar
+package com.android.systemui.navigationbar.views.buttons
 
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavbarOrientationTrackingLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavbarOrientationTrackingLogger.kt
index b1bd286..a5ba17b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavbarOrientationTrackingLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar
+package com.android.systemui.navigationbar.views.buttons
 
 import android.view.Surface
 import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java
index d780f7c..e1a1a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ReverseLinearLayout.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ReverseLinearLayout.java
index f1e1366..9d50bd6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ReverseLinearLayout.java
@@ -1,18 +1,20 @@
 /*
  * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import android.annotation.Nullable;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index c214361..ff20707 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -74,7 +74,7 @@
 
     @Binds
     @PaginatedBaseLayoutType
-    fun bindPaginatedBaseGridLayout(impl: PartitionedGridLayout): PaginatableGridLayout
+    fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout
 
     @Binds
     @PaginatedBaseLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
new file mode 100644
index 0000000..f7c71ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.emitOnStart
+import javax.inject.Inject
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class QuickQuickSettingsRowRepository
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    configurationRepository: ConfigurationRepository,
+) {
+    val rows =
+        configurationRepository.onConfigurationChange.emitOnStart().map {
+            resources.getInteger(R.integer.quick_qs_panel_max_rows)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractor.kt
new file mode 100644
index 0000000..5f001df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.QuickQuickSettingsRowRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickQuickSettingsRowInteractor
+@Inject
+constructor(
+    quickQuickSettingsRowRepository: QuickQuickSettingsRowRepository,
+) {
+    val rows = quickQuickSettingsRowRepository.rows
+
+    val defaultRows = 2
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
new file mode 100644
index 0000000..c0371b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
+import com.android.systemui.res.R
+
+@Composable
+fun QuickQuickSettings(
+    viewModel: QuickQuickSettingsViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val sizedTiles by
+        viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList())
+    val tiles = sizedTiles.map { it.tile }
+
+    DisposableEffect(tiles) {
+        val token = Any()
+        tiles.forEach { it.startListening(token) }
+        onDispose { tiles.forEach { it.stopListening(token) } }
+    }
+    val columns by viewModel.columns.collectAsStateWithLifecycle()
+
+    TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
+        items(
+            tiles.size,
+            key = { index -> sizedTiles[index].tile.spec },
+            span = { index -> GridItemSpan(sizedTiles[index].width) }
+        ) { index ->
+            Tile(
+                tile = tiles[index],
+                iconOnly = sizedTiles[index].width == 1,
+                modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
new file mode 100644
index 0000000..b3acace
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class QuickQuickSettingsViewModel
+@Inject
+constructor(
+    tilesInteractor: CurrentTilesInteractor,
+    fixedColumnsSizeViewModel: FixedColumnsSizeViewModel,
+    quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
+    private val iconTilesViewModel: IconTilesViewModel,
+    @Application private val applicationScope: CoroutineScope,
+) {
+
+    val columns = fixedColumnsSizeViewModel.columns
+
+    private val rows =
+        quickQuickSettingsRowInteractor.rows.stateIn(
+            applicationScope,
+            SharingStarted.WhileSubscribed(),
+            quickQuickSettingsRowInteractor.defaultRows
+        )
+
+    val tileViewModels: Flow<List<SizedTile<TileViewModel>>> =
+        columns.flatMapLatest { columns ->
+            tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
+                tiles
+                    .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+                    .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+            }
+        }
+
+    private val TileSpec.width: Int
+        get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
+
+    companion object {
+        private fun splitInRowsSequence(
+            tiles: List<SizedTile<TileViewModel>>,
+            columns: Int,
+        ): Sequence<List<SizedTile<TileViewModel>>> = sequence {
+            val row = TileRow<TileViewModel>(columns)
+            for (tile in tiles) {
+                check(tile.width <= columns)
+                if (!row.maybeAddTile(tile)) {
+                    // Couldn't add tile to previous row, create a row with the current tiles
+                    // and start a new one
+                    yield(row.tiles)
+                    row.clear()
+                    row.maybeAddTile(tile)
+                }
+            }
+            if (row.tiles.isNotEmpty()) {
+                yield(row.tiles)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index a04fa38..6ccd169 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
 import javax.inject.Inject
 
@@ -29,4 +30,5 @@
     val brightnessSliderViewModel: BrightnessSliderViewModel,
     val tileGridViewModel: TileGridViewModel,
     val editModeViewModel: EditModeViewModel,
+    val quickQuickSettingsViewModel: QuickQuickSettingsViewModel,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index e395d30..66fcbac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -20,42 +20,54 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
-import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeAlignment
 import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the Quick Settings Shade scene. */
 @SysUISingleton
 class QuickSettingsShadeSceneViewModel
 @Inject
 constructor(
-    shadeInteractor: ShadeInteractor,
+    private val shadeInteractor: ShadeInteractor,
     val overlayShadeViewModel: OverlayShadeViewModel,
-    val brightnessSliderViewModel: BrightnessSliderViewModel,
-    val tileGridViewModel: TileGridViewModel,
-    val editModeViewModel: EditModeViewModel,
-    val qsSceneAdapter: QSSceneAdapter,
+    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+    @Application applicationScope: CoroutineScope,
 ) {
+
+    val isEditing: StateFlow<Boolean> = quickSettingsContainerViewModel.editModeViewModel.isEditing
+
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        MutableStateFlow(
-                mapOf(
-                    if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
-                        Swipe.Up
-                    } else {
-                        Swipe.Down
-                    } to SceneFamilies.Home,
-                    Back to SceneFamilies.Home,
-                )
+        isEditing
+            .map { editing -> destinations(editing) }
+            .stateIn(
+                applicationScope,
+                SharingStarted.WhileSubscribed(),
+                destinations(isEditing.value)
             )
-            .asStateFlow()
+
+    private fun destinations(editing: Boolean): Map<UserAction, UserActionResult> {
+        return buildMap {
+            put(
+                if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+                    Swipe.Up
+                } else {
+                    Swipe.Down
+                },
+                UserActionResult(SceneFamilies.Home)
+            )
+            if (!editing) {
+                put(Back, UserActionResult(SceneFamilies.Home))
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4b82e0f..e07b057 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -91,11 +91,11 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationBar;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarView;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 2b717cb..8d0a386 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -57,8 +57,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 59c1592..ba4c29a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -47,8 +47,6 @@
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -150,8 +148,6 @@
     /**
      * True if either the primary or alternate bouncer are open, meaning the hub should not receive
      * any touch input.
-     *
-     * Tracks [KeyguardTransitionInteractor.isFinishedInState] for [KeyguardState.isBouncerState].
      */
     private var anyBouncerShowing = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6b4e44f..04de2c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -158,8 +158,8 @@
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 89b7ccf..e505ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -203,7 +203,9 @@
     @Override
     public void start() {
         mJavaAdapter.alwaysCollectFlow(
-                mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
+                mKeyguardTransitionInteractorLazy.get().isFinishedIn(
+                        /* scene */ Scenes.Gone,
+                        /* stateWithoutSceneContainer */ GONE),
                 (Boolean isFinishedInState) -> {
                     if (isFinishedInState) {
                         setLeaveOpenOnKeyguardHide(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index e9306a5..069ae93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -41,4 +41,7 @@
     val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
 
     fun setHeadsUpAnimatingAway(animatingAway: Boolean)
+
+    /** Snooze the currently pinned HUN. */
+    fun snooze()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 4a6553f..cdab108 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -101,10 +101,17 @@
 
     fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
         HeadsUpRowInteractor(key as HeadsUpRowRepository)
+
     fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+
     fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
         headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
     }
+
+    /** Snooze the currently pinned HUN. */
+    fun snooze() {
+        headsUpRepository.snooze()
+    }
 }
 
 class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 48c89f8..5e91786 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.database.ContentObserver
 import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Bundle
 import android.os.Handler
 import android.os.PowerManager
 import android.os.SystemProperties
@@ -368,6 +369,11 @@
             PendingIntent.FLAG_IMMUTABLE
         )
 
+        // Replace "System UI" app name with "Android System"
+        val bundle = Bundle()
+        bundle.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                context.getString(com.android.internal.R.string.android_system_label))
+
         val builder =
             Notification.Builder(context, NotificationChannels.ALERTS)
                 .setTicker(titleStr)
@@ -375,9 +381,11 @@
                 .setContentText(textStr)
                 .setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
                 .setCategory(Notification.CATEGORY_SYSTEM)
+                .setTimeoutAfter(/* one day in ms */ 24 * 60 * 60 * 1000L)
                 .setAutoCancel(true)
                 .addAction(android.R.drawable.button_onoff_indicator_off, actionStr, pendingIntent)
                 .setContentIntent(pendingIntent)
+                .addExtras(bundle)
 
         notificationManager.notify(SystemMessage.NOTE_ADAPTIVE_NOTIFICATIONS, builder.build())
         hasSeenEdu = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 4a043d3..9e943ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -104,6 +104,7 @@
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
+import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -1584,6 +1585,9 @@
                 /* headerView= */ headerView,
                 /* onClickListener= */ mExpandClickListener
         );
+        if (TransparentHeaderFix.isEnabled()) {
+            updateBackgroundForGroupState();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt
new file mode 100644
index 0000000..0b01510
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification transparent header fix flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object TransparentHeaderFix {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the flag enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationTransparentHeaderFix()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 715c6e6..cb03334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3499,6 +3499,13 @@
         setIsBeingDragged(true);
     }
 
+    // Only when scene container is enabled, mark that we are being dragged so that we start
+    // dispatching the rest of the gesture to scene container.
+    void startDraggingOnHun() {
+        SceneContainerFlag.isUnexpectedlyInLegacyMode();
+        setIsBeingDragged(true);
+    }
+
     @Override
     public boolean onGenericMotionEvent(MotionEvent event) {
         if (!isScrollingEnabled()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 12f8f69..58b72e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -59,6 +59,7 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.view.OneShotPreDrawListener;
 import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
@@ -126,6 +127,7 @@
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -168,6 +170,7 @@
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
     private final HeadsUpManager mHeadsUpManager;
+    private HeadsUpTouchHelper mHeadsUpTouchHelper;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
     private final TunerService mTunerService;
     private final DeviceProvisionedController mDeviceProvisionedController;
@@ -706,6 +709,7 @@
             NotificationVisibilityProvider visibilityProvider,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             HeadsUpManager headsUpManager,
+            Provider<IStatusBarService> statusBarService,
             NotificationRoundnessManager notificationRoundnessManager,
             TunerService tunerService,
             DeviceProvisionedController deviceProvisionedController,
@@ -758,6 +762,14 @@
         mVisibilityProvider = visibilityProvider;
         mWakeUpCoordinator = wakeUpCoordinator;
         mHeadsUpManager = headsUpManager;
+        if (SceneContainerFlag.isEnabled()) {
+            mHeadsUpTouchHelper = new HeadsUpTouchHelper(
+                    mHeadsUpManager,
+                    statusBarService.get(),
+                    getHeadsUpCallback(),
+                    new HeadsUpNotificationViewControllerEmptyImpl()
+            );
+        }
         mNotificationRoundnessManager = notificationRoundnessManager;
         mTunerService = tunerService;
         mDeviceProvisionedController = deviceProvisionedController;
@@ -2000,6 +2012,13 @@
                     && !mView.isExpandingNotification()) {
                 scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
             }
+            boolean hunWantsIt = false;
+            if (shouldHeadsUpHandleTouch()) {
+                hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
+                if (hunWantsIt) {
+                    mView.startDraggingOnHun();
+                }
+            }
             boolean swipeWantsIt = false;
             if (mLongPressedView == null && !mView.isBeingDragged()
                     && !mView.isExpandingNotification()
@@ -2029,7 +2048,7 @@
                     && ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
                 mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
             }
-            return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
+            return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt || hunWantsIt;
         }
 
         @Override
@@ -2081,6 +2100,10 @@
                     && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
                 scrollerWantsIt = mView.onScrollTouch(ev);
             }
+            boolean hunWantsIt = false;
+            if (shouldHeadsUpHandleTouch()) {
+                hunWantsIt = mHeadsUpTouchHelper.onTouchEvent(ev);
+            }
 
             // Check if we need to clear any snooze leavebehinds
             if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
@@ -2101,7 +2124,8 @@
                 mView.setCheckForLeaveBehind(true);
             }
             traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
-            return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
+            return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt
+                    || hunWantsIt;
         }
 
         private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
@@ -2128,6 +2152,11 @@
                     break;
             }
         }
+
+        private boolean shouldHeadsUpHandleTouch() {
+            return SceneContainerFlag.isEnabled() && mLongPressedView == null
+                    && !mSwipeHelper.isSwiping();
+        }
     }
 
     private class NotifStackControllerImpl implements NotifStackController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 08e81d5..eb1f778 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -43,6 +44,7 @@
     private val interactor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
     private val shadeSceneViewModel: ShadeSceneViewModel,
+    private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
     featureFlags: FeatureFlagsClassic,
 ) : FlowDumperImpl(dumpManager) {
     /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
@@ -74,6 +76,10 @@
     /** Whether or not the notification scrim should be clickable. */
     val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable
 
+    /** True when a HUN is pinned or animating away. */
+    val isHeadsUpOrAnimatingAway: Flow<Boolean> =
+        headsUpNotificationInteractor.isHeadsUpOrAnimatingAway
+
     /** Corner rounding of the stack */
     val shadeScrimRounding: Flow<ShadeScrimRounding> =
         interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
@@ -103,6 +109,11 @@
     fun setScrolledToTop(scrolledToTop: Boolean) {
         interactor.setScrolledToTop(scrolledToTop)
     }
+
+    /** Snooze the currently pinned HUN. */
+    fun snoozeHun() {
+        headsUpNotificationInteractor.snooze()
+    }
 }
 
 // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
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 8e58ffb..1c57346 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
@@ -287,7 +287,7 @@
     /** Are we on the dream without the shade/qs? */
     private val isDreamingWithoutShade: Flow<Boolean> =
         combine(
-                keyguardTransitionInteractor.isFinishedInState(DREAMING),
+                keyguardTransitionInteractor.isFinishedIn(DREAMING),
                 isAnyExpanded,
             ) { isDreaming, isAnyExpanded ->
                 isDreaming && !isAnyExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 0a02381..a0d4ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -41,7 +41,7 @@
 import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.RemoteAnimationRunnerCompat;
 import com.android.systemui.display.data.repository.DisplayMetricsRepository;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 3bc8e27..88d3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -20,7 +20,7 @@
 import androidx.lifecycle.LifecycleRegistry
 import com.android.keyguard.AuthKeyguardMessageArea
 import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.navigationbar.NavigationBarView
+import com.android.systemui.navigationbar.views.NavigationBarView
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.qs.QSPanelController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 491db30..462ae7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -146,7 +146,7 @@
 import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..9f76429
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController
+
+/** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
+class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
+    override fun setHeadsUpDraggingStartingHeight(startHeight: Int) {}
+
+    override fun setTrackedHeadsUp(expandableNotificationRow: ExpandableNotificationRow?) {}
+
+    override fun startExpand(
+        newX: Float,
+        newY: Float,
+        startTracking: Boolean,
+        expandedHeight: Float
+    ) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 198272e..7f16e18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -122,9 +123,14 @@
                                                 + mPickedChild.getTranslationY());
                     mPanel.setHeadsUpDraggingStartingHeight(startHeight);
                     mPanel.startExpand(x, y, true /* startTracking */, startHeight);
-                    // This call needs to be after the expansion start otherwise we will get a
-                    // flicker of one frame as it's not expanded yet.
-                    mHeadsUpManager.unpinAll(true);
+
+                    // TODO(b/340514839): Figure out where to move this side effect in flexiglass
+                    if (!SceneContainerFlag.isEnabled()) {
+                        // This call needs to be after the expansion start otherwise we will get a
+                        // flicker of one frame as it's not expanded yet.
+                        mHeadsUpManager.unpinAll(true);
+                    }
+
                     clearNotificationEffects();
                     endMotion();
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b40bf56..0316b0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -77,9 +77,9 @@
 import com.android.systemui.keyguard.shared.model.KeyguardDone;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
+import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 9449659..62bd8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -349,6 +349,7 @@
                     false
                 }
             }
+            .flowOn(bgDispatcher)
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val carrierId =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 3e01180..4869114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -20,7 +20,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.policy.BatteryController
@@ -48,6 +49,7 @@
 constructor(
     @Application scope: CoroutineScope,
     headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+    sceneInteractor: SceneInteractor,
     keyguardInteractor: KeyguardInteractor,
     keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
     batteryController: BatteryController,
@@ -55,11 +57,11 @@
     /** True if this view should be visible and false otherwise. */
     val isVisible: StateFlow<Boolean> =
         combine(
+                sceneInteractor.currentScene,
                 keyguardInteractor.isDozing,
-                keyguardInteractor.statusBarState,
                 headsUpNotificationInteractor.showHeadsUpStatusBar,
-            ) { isDozing, statusBarState, showHeadsUpStatusBar ->
-                !isDozing && statusBarState == StatusBarState.KEYGUARD && !showHeadsUpStatusBar
+            ) { currentScene, isDozing, showHeadsUpStatusBar ->
+                currentScene == Scenes.Lockscreen && !isDozing && !showHeadsUpStatusBar
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 4963aae..c7fc445 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -202,8 +202,8 @@
             }
             boolean currentUser = userId == mUserTracker.getUserId();
             boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
-                    ? mKeyguardTransitionInteractor.isFinishedInStateWhereValue(
-                    KeyguardState.Companion::deviceIsAsleepInState)
+                    ? KeyguardState.Companion.deviceIsAsleepInState(
+                            mKeyguardTransitionInteractor.getFinishedState())
                     : mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
 
             if (currentUser && !mAcceptColorEvents && isAsleep) {
@@ -525,7 +525,7 @@
 
         if (themeOverlayControllerWakefulnessDeprecation()) {
             mJavaAdapter.alwaysCollectFlow(
-                    mKeyguardTransitionInteractor.isFinishedInState(KeyguardState.DOZING),
+                    mKeyguardTransitionInteractor.isFinishedIn(KeyguardState.DOZING),
                     isFinishedInDozing -> {
                         if (isFinishedInDozing) whenAsleepHandler.run();
                     });
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index ec6f862..fb40235 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -14,18 +14,18 @@
 
 package com.android.systemui.tuner;
 
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_END;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_START;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.MENU_IME_ROTATE;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAVSPACE;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_LEFT;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_RIGHT;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractImage;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractKeycode;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY_CODE_END;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY_CODE_START;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY_IMAGE_DELIM;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.MENU_IME_ROTATE;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAV_BAR_RIGHT;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAV_BAR_VIEWS;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.extractButton;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.extractKeycode;
 
 import android.annotation.Nullable;
 import android.app.AlertDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 8c46f9a..28ac2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -106,3 +106,13 @@
 ): Flow<R> {
     return combine(flow1, flow2, flow3, trifunction)
 }
+
+fun <T1, T2, T3, T4, R> combineFlows(
+    flow: Flow<T1>,
+    flow2: Flow<T2>,
+    flow3: Flow<T3>,
+    flow4: Flow<T4>,
+    transform: (T1, T2, T3, T4) -> R
+): Flow<R> {
+    return combine(flow, flow2, flow3, flow4, transform)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index aee441a..c45f98e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -567,7 +567,7 @@
             streamStateW(stream).levelMax = Math.max(1, getAudioManagerStreamMaxVolume(stream));
             updateStreamMuteW(stream, mAudio.isStreamMute(stream));
             final StreamState ss = streamStateW(stream);
-            ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
+            ss.muteSupported = mAudio.isStreamMutableByUi(stream);
             ss.name = STREAMS.get(stream);
             checkRoutedToBluetoothW(stream);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 64c162f..639b53b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleCoroutineScope;
 import androidx.lifecycle.LifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
@@ -159,7 +160,9 @@
             ArgumentCaptor<LifecycleObserver> observerCaptor =
                     ArgumentCaptor.forClass(LifecycleObserver.class);
             verify(mLifecycleRegistry, atLeast(1)).addObserver(observerCaptor.capture());
-            mLifecycleObservers.addAll(observerCaptor.getAllValues());
+            mLifecycleObservers.addAll(observerCaptor.getAllValues().stream().filter(
+                    lifecycleObserver -> !(lifecycleObserver instanceof LifecycleCoroutineScope))
+                    .toList());
 
             updateLifecycle(Lifecycle.State.RESUMED);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 1c327af..f34058b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -20,6 +20,7 @@
 import android.os.Looper
 import android.testing.TestableLooper
 import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.Display.TYPE_EXTERNAL
 import android.view.Display.TYPE_INTERNAL
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -27,7 +28,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -45,6 +45,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
 
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
@@ -63,8 +64,8 @@
 
     @Before
     fun setup() {
-        setDisplays(emptyList())
-        setAllDisplaysIncludingDisabled()
+        setDisplays(DEFAULT_DISPLAY)
+        setAllDisplaysIncludingDisabled(DEFAULT_DISPLAY)
         displayRepository =
             DisplayRepositoryImpl(
                 displayManager,
@@ -81,7 +82,7 @@
         testScope.runTest {
             val value by latestDisplayFlowValue()
 
-            assertThat(value).isEmpty()
+            assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
 
             verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
         }
@@ -91,7 +92,7 @@
         testScope.runTest {
             val value by latestDisplayFlowValue()
 
-            assertThat(value).isEmpty()
+            assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
 
             verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
         }
@@ -103,14 +104,14 @@
         testScope.runTest {
             val firstSubscriber by latestDisplayFlowValue()
 
-            assertThat(firstSubscriber).isEmpty()
+            assertThat(firstSubscriber).hasSize(1) // Default display only
             verify(displayManager, times(1))
                 .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
 
             val innerScope = TestScope()
             innerScope.runTest {
                 val secondSubscriber by latestDisplayFlowValue()
-                assertThat(secondSubscriber).isEmpty()
+                assertThat(secondSubscriber).hasSize(1)
 
                 // No new registration, just the precedent one.
                 verify(displayManager, times(1))
@@ -120,7 +121,7 @@
             // Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
             setDisplays(1)
             sendOnDisplayAdded(1)
-            assertThat(firstSubscriber?.ids()).containsExactly(1)
+            assertThat(firstSubscriber?.ids()).contains(1)
         }
 
         // All subscribers are done, unregister should have been called.
@@ -135,7 +136,7 @@
             setDisplays(1)
             sendOnDisplayAdded(1)
 
-            assertThat(value?.ids()).containsExactly(1)
+            assertThat(value?.ids()).contains(1)
         }
 
     @Test
@@ -152,7 +153,7 @@
             setDisplays(1, 2, 3)
             sendOnDisplayRemoved(4)
 
-            assertThat(value?.ids()).containsExactly(1, 2, 3)
+            assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY, 1, 2, 3)
         }
 
     @Test
@@ -168,7 +169,7 @@
 
             displayListener.value.onDisplayChanged(4)
 
-            assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
+            assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY, 1, 2, 3, 4)
         }
 
     @Test
@@ -435,37 +436,29 @@
             val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
             setDisplays(
                 listOf(
-                    display(
-                        type = TYPE_INTERNAL,
-                        id = Display.DEFAULT_DISPLAY,
-                        state = Display.STATE_OFF
-                    )
+                    display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_OFF)
                 )
             )
-            displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+            displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
             assertThat(defaultDisplayOff).isTrue()
 
             setDisplays(
                 listOf(
-                    display(
-                        type = TYPE_INTERNAL,
-                        id = Display.DEFAULT_DISPLAY,
-                        state = Display.STATE_ON
-                    )
+                    display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
                 )
             )
-            displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+            displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
             assertThat(defaultDisplayOff).isFalse()
         }
 
     @Test
     fun displayFlow_startsWithDefaultDisplayBeforeAnyEvent() =
         testScope.runTest {
-            setDisplays(Display.DEFAULT_DISPLAY)
+            setDisplays(DEFAULT_DISPLAY)
 
             val value by latestDisplayFlowValue()
 
-            assertThat(value?.ids()).containsExactly(Display.DEFAULT_DISPLAY)
+            assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
         }
 
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
@@ -565,6 +558,8 @@
     }
 
     private fun setDisplays(vararg ids: Int) {
-        setDisplays(ids.map { display(type = TYPE_EXTERNAL, id = it) })
+        // DEFAULT_DISPLAY always there
+        val idsToSet = ids.toSet() + DEFAULT_DISPLAY
+        setDisplays(idsToSet.map { display(type = TYPE_EXTERNAL, id = it) })
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
index 6398a5a..c1bd378 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
@@ -19,26 +19,22 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.keyguardUpdateMonitor
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
-import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 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.statusbar.policy.keyguardStateController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.whenever
+import org.junit.runners.JUnit4
 
 @ExperimentalCoroutinesApi
 @RunWith(AndroidJUnit4::class)
@@ -54,35 +50,13 @@
     fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() =
         testScope.runTest {
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-            val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
-            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsUdfps()
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepToLockscreen(0f, TransitionState.STARTED),
-                    stepToLockscreen(.4f),
-                    stepToLockscreen(1f, TransitionState.FINISHED),
-                ),
-                testScope,
-            )
-            assertThat(canShowAlternateBouncer).isTrue()
-            transitionRepository.sendTransitionSteps(
-                listOf(
-                    stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromLockscreenToAlternateBouncer(.4f),
-                    stepFromLockscreenToAlternateBouncer(.6f),
-                ),
-                testScope,
-            )
-            assertThat(canShowAlternateBouncer).isTrue()
-            assertThat(alternateBouncerWindowRequired).isTrue()
-
-            transitionRepository.sendTransitionSteps(
-                listOf(
                     stepFromAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromAlternateBouncer(.2f),
+                    stepFromAlternateBouncer(.4f),
                     stepFromAlternateBouncer(.6f),
                 ),
                 testScope,
@@ -104,21 +78,13 @@
             mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
-            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsUdfps()
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepToLockscreen(0f, TransitionState.STARTED),
-                    stepToLockscreen(.4f),
-                    stepToLockscreen(1f, TransitionState.FINISHED),
-                ),
-                testScope,
-            )
-            transitionRepository.sendTransitionSteps(
-                listOf(
-                    stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromLockscreenToAlternateBouncer(.4f),
-                    stepFromLockscreenToAlternateBouncer(.6f),
+                    stepFromAlternateBouncer(0f, TransitionState.STARTED),
+                    stepFromAlternateBouncer(.4f),
+                    stepFromAlternateBouncer(.6f),
+                    stepFromAlternateBouncer(1f),
                 ),
                 testScope,
             )
@@ -131,23 +97,13 @@
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
-            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsUdfps()
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepFromLockscreenToDozing(0f, TransitionState.STARTED),
-                    stepFromLockscreenToDozing(.4f),
-                    stepFromLockscreenToDozing(.6f),
-                    stepFromLockscreenToDozing(1f, TransitionState.FINISHED),
-                ),
-                testScope,
-            )
-            assertThat(alternateBouncerWindowRequired).isFalse()
-            transitionRepository.sendTransitionSteps(
-                listOf(
                     stepFromDozingToLockscreen(0f, TransitionState.STARTED),
                     stepFromDozingToLockscreen(.4f),
                     stepFromDozingToLockscreen(.6f),
+                    stepFromDozingToLockscreen(1f),
                 ),
                 testScope,
             )
@@ -160,39 +116,19 @@
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
-            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsRearFps()
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepToLockscreen(0f, TransitionState.STARTED),
-                    stepToLockscreen(.4f),
-                    stepToLockscreen(1f, TransitionState.FINISHED),
-                ),
-                testScope,
-            )
-            transitionRepository.sendTransitionSteps(
-                listOf(
-                    stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromLockscreenToAlternateBouncer(.4f),
-                    stepFromLockscreenToAlternateBouncer(.6f),
+                    stepFromAlternateBouncer(0f, TransitionState.STARTED),
+                    stepFromAlternateBouncer(.4f),
+                    stepFromAlternateBouncer(.6f),
+                    stepFromAlternateBouncer(1f),
                 ),
                 testScope,
             )
             assertThat(alternateBouncerWindowRequired).isFalse()
         }
 
-    private fun stepToLockscreen(
-        value: Float,
-        state: TransitionState = TransitionState.RUNNING
-    ): TransitionStep {
-        return step(
-            from = KeyguardState.GONE,
-            to = KeyguardState.LOCKSCREEN,
-            value = value,
-            transitionState = state,
-        )
-    }
-
     private fun stepFromAlternateBouncer(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
@@ -205,18 +141,6 @@
         )
     }
 
-    private fun stepFromLockscreenToAlternateBouncer(
-        value: Float,
-        state: TransitionState = TransitionState.RUNNING
-    ): TransitionStep {
-        return step(
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.ALTERNATE_BOUNCER,
-            value = value,
-            transitionState = state,
-        )
-    }
-
     private fun stepFromDozingToLockscreen(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
@@ -229,18 +153,6 @@
         )
     }
 
-    private fun stepFromLockscreenToDozing(
-        value: Float,
-        state: TransitionState = TransitionState.RUNNING
-    ): TransitionStep {
-        return step(
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.DOZING,
-            value = value,
-            transitionState = state,
-        )
-    }
-
     private fun step(
         from: KeyguardState,
         to: KeyguardState,
@@ -255,16 +167,4 @@
             ownerName = "AlternateBouncerViewModelTest"
         )
     }
-
-    /**
-     * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside
-     * from the fingerprint modality.
-     */
-    private fun givenCanShowAlternateBouncer() {
-        kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false)
-        kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
-        kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
-        whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
-        whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index df8eafe..196bbb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.views.NavigationBar;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shared.recents.utilities.Utilities;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
index 85244fd..00e79f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -38,6 +38,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.FakeDisplayTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
index a358c18..e58c8f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doNothing;
@@ -32,7 +32,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 982a269..98ff6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
@@ -28,7 +28,7 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
-import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -39,7 +39,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -86,10 +86,15 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.DeadZone;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.NavBarHelper;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
+import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
index c7a92d2..3621ab9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -32,6 +32,8 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
index 841f620..403a883 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import static android.view.KeyEvent.ACTION_DOWN;
 import static android.view.KeyEvent.ACTION_UP;
@@ -26,12 +26,12 @@
 import static android.view.KeyEvent.KEYCODE_HOME;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
 
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
index 7eb7c8e..79c4a96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
index 6a3c615..7941a68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ce2491b..1060b62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -53,6 +53,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -99,6 +100,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
 import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -135,6 +137,8 @@
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback;
+    @Mock private Provider<IStatusBarService> mStatusBarService;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private TunerService mTunerService;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -973,6 +977,8 @@
         when(mNotificationStackScrollLayout.getViewTreeObserver())
                 .thenReturn(viewTreeObserver);
         when(mNotificationStackScrollLayout.getContext()).thenReturn(getContext());
+        when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
+        when(mHeadsUpCallback.getContext()).thenReturn(getContext());
         mController = new NotificationStackScrollLayoutController(
                 mNotificationStackScrollLayout,
                 true,
@@ -981,6 +987,7 @@
                 mVisibilityProvider,
                 mNotificationWakeUpCoordinator,
                 mHeadsUpManager,
+                mStatusBarService,
                 mNotificationRoundnessManager,
                 mTunerService,
                 mDeviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 53e643e..0cb28cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -52,28 +52,17 @@
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.TestScopeProvider;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
 import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeViewStateProvider;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository;
-import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
 import com.android.systemui.statusbar.phone.ui.TintedIconManager;
@@ -82,14 +71,11 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
-import kotlinx.coroutines.test.TestScope;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -150,11 +136,7 @@
     private KeyguardStatusBarViewController mController;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
-    private final TestScope mTestScope = TestScopeProvider.getTestScope();
-    private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-    private KeyguardInteractor mKeyguardInteractor;
-    private KeyguardStatusBarViewModel mViewModel;
 
     @Before
     public void setup() throws Exception {
@@ -163,28 +145,6 @@
         MockitoAnnotations.initMocks(this);
 
         when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
-        KeyguardTransitionInteractor keyguardTransitionInteractor =
-                mKosmos.getKeyguardTransitionInteractor();
-        mKeyguardInteractor = new KeyguardInteractor(
-                mKeyguardRepository,
-                mCommandQueue,
-                PowerInteractorFactory.create().getPowerInteractor(),
-                new FakeKeyguardBouncerRepository(),
-                new ConfigurationInteractor(new FakeConfigurationRepository()),
-                new FakeShadeRepository(),
-                keyguardTransitionInteractor,
-                () -> mKosmos.getSceneInteractor(),
-                () -> mKosmos.getFromGoneTransitionInteractor(),
-                () -> mKosmos.getFromLockscreenTransitionInteractor(),
-                () -> mKosmos.getSharedNotificationContainerInteractor(),
-                mTestScope);
-        mViewModel =
-                new KeyguardStatusBarViewModel(
-                        mTestScope.getBackgroundScope(),
-                        mKosmos.getHeadsUpNotificationInteractor(),
-                        mKeyguardInteractor,
-                        new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
-                        mBatteryController);
 
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
@@ -212,7 +172,7 @@
                 mKeyguardStateController,
                 mKeyguardBypassController,
                 mKeyguardUpdateMonitor,
-                mViewModel,
+                mKosmos.getKeyguardStatusBarViewModel(),
                 mBiometricUnlockController,
                 mStatusBarStateController,
                 mStatusBarContentInsetsProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index d88289d..ef4e734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -26,8 +26,10 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -85,6 +87,7 @@
             KeyguardStatusBarViewModel(
                 testScope.backgroundScope,
                 headsUpNotificationInteractor,
+                kosmos.sceneInteractor,
                 keyguardInteractor,
                 keyguardStatusBarInteractor,
                 batteryController,
@@ -95,7 +98,7 @@
     fun isVisible_dozing_false() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isVisible)
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
 
             keyguardRepository.setIsDozing(true)
 
@@ -103,21 +106,21 @@
         }
 
     @Test
-    fun isVisible_statusBarStateShade_false() =
+    fun isVisible_sceneShade_false() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isVisible)
 
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
 
             assertThat(latest).isFalse()
         }
 
     @Test
-    fun isVisible_statusBarStateShadeLocked_false() =
+    fun isVisible_sceneBouncer_false() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isVisible)
 
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
 
             assertThat(latest).isFalse()
         }
@@ -130,7 +133,7 @@
             // WHEN HUN displayed on the bypass lock screen
             headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
             keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
             faceAuthRepository.isBypassEnabled.value = true
 
             // THEN KeyguardStatusBar is NOT visible to make space for HeadsUpStatusBar
@@ -138,11 +141,11 @@
         }
 
     @Test
-    fun isVisible_statusBarStateKeyguard_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
+    fun isVisible_sceneLockscreen_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isVisible)
 
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
             keyguardRepository.setIsDozing(false)
 
             assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 5db9d31..9dae44d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -16,7 +16,6 @@
 package com.android.systemui
 
 import android.app.ActivityManager
-import android.app.DreamManager
 import android.app.admin.DevicePolicyManager
 import android.app.trust.TrustManager
 import android.os.UserManager
@@ -33,7 +32,6 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.ScreenLifecycle
@@ -95,7 +93,6 @@
     @get:Provides val demoModeController: DemoModeController = mock(),
     @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
     @get:Provides val dozeParameters: DozeParameters = mock(),
-    @get:Provides val dreamManager: DreamManager = mock(),
     @get:Provides val dumpManager: DumpManager = mock(),
     @get:Provides val headsUpManager: HeadsUpManager = mock(),
     @get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
@@ -133,7 +130,6 @@
     @get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
     @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
     @get:Provides val communalInteractor: CommunalInteractor = mock(),
-    @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
     @get:Provides val sceneLogger: SceneLogger = mock(),
     @get:Provides val trustManager: TrustManager = mock(),
     @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index 744b127..edf77a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -16,9 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.service.dream.dreamManager
 import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
@@ -37,10 +35,8 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             communalInteractor = communalInteractor,
-            communalSceneInteractor = communalSceneInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
             deviceEntryRepository = deviceEntryRepository,
-            dreamManager = dreamManager
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 3c62b44..b5e6f75 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.ui.systemBarUtilsProxy
 
 val Kosmos.keyguardClockViewModel by
@@ -32,7 +31,6 @@
             keyguardClockInteractor = keyguardClockInteractor,
             applicationScope = applicationCoroutineScope,
             aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
-            notifsKeyguardInteractor = notificationsKeyguardInteractor,
             shadeInteractor = shadeInteractor,
             systemBarUtils = systemBarUtilsProxy,
             configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 10ff731..0666820 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiInteractor
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
 import com.android.systemui.util.time.systemClock
 import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -102,6 +103,7 @@
     val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+    val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
     val powerRepository by lazy { kosmos.fakePowerRepository }
     val clock by lazy { kosmos.systemClock }
     val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryKosmos.kt
new file mode 100644
index 0000000..da55e21
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+
+val Kosmos.quickQuickSettingsRowRepository by
+    Kosmos.Fixture {
+        testCase.context.orCreateTestableResources
+        QuickQuickSettingsRowRepository(testCase.context.resources, configurationRepository)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractorKosmos.kt
new file mode 100644
index 0000000..3fcbc8d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.quickQuickSettingsRowRepository
+
+val Kosmos.quickQuickSettingsRowInteractor by
+    Kosmos.Fixture { QuickQuickSettingsRowInteractor(quickQuickSettingsRowRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
new file mode 100644
index 0000000..40d2624
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.panels.domain.interactor.quickQuickSettingsRowInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.quickQuickSettingsViewModel by
+    Kosmos.Fixture {
+        QuickQuickSettingsViewModel(
+            currentTilesInteractor,
+            fixedColumnsSizeViewModel,
+            quickQuickSettingsRowInteractor,
+            iconTilesViewModel,
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
new file mode 100644
index 0000000..779634d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
+
+val Kosmos.quickSettingsContainerViewModel by
+    Kosmos.Fixture {
+        QuickSettingsContainerViewModel(
+            brightnessSliderViewModel,
+            tileGridViewModel,
+            editModeViewModel,
+            quickQuickSettingsViewModel,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index 8adb26f..c56c56c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -14,24 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shade.ui.viewmodel
+package com.android.systemui.qs.ui.viewmodel
 
-import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
-import com.android.systemui.qs.ui.adapter.qsSceneAdapter
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
 
 val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
     Kosmos.Fixture {
         QuickSettingsShadeSceneViewModel(
-            overlayShadeViewModel = overlayShadeViewModel,
-            brightnessSliderViewModel = brightnessSliderViewModel,
-            tileGridViewModel = tileGridViewModel,
-            editModeViewModel = editModeViewModel,
-            qsSceneAdapter = qsSceneAdapter,
             shadeInteractor = shadeInteractor,
+            overlayShadeViewModel = overlayShadeViewModel,
+            quickSettingsContainerViewModel = quickSettingsContainerViewModel,
+            applicationScope = applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
index 641a757..2f17ca8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -18,8 +18,11 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,6 +33,25 @@
 private val mutableTransitionState =
     MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen))
 
+suspend fun Kosmos.setTransition(
+    sceneTransition: ObservableTransitionState,
+    stateTransition: TransitionStep? = null,
+    scope: TestScope = testScope,
+    repository: SceneContainerRepository = sceneContainerRepository
+) {
+    if (SceneContainerFlag.isEnabled) {
+        setSceneTransition(sceneTransition, scope, repository)
+    } else {
+        if (stateTransition == null) throw IllegalArgumentException("No transitionStep provided")
+        fakeKeyguardTransitionRepository.sendTransitionSteps(
+            from = stateTransition.from,
+            to = stateTransition.to,
+            testScope = scope,
+            throughTransitionState = stateTransition.transitionState
+        )
+    }
+}
+
 fun Kosmos.setSceneTransition(
     transition: ObservableTransitionState,
     scope: TestScope = testScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 7bf77e5..492e87b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,7 +30,12 @@
     override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
     override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
         MutableStateFlow(emptySet())
+
     override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
         isHeadsUpAnimatingAway.value = animatingAway
     }
+
+    override fun snooze() {
+        // do nothing
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index a0e9303..afb8acb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
 val Kosmos.notificationsPlaceholderViewModel by Fixture {
@@ -30,6 +31,7 @@
         interactor = notificationStackAppearanceInteractor,
         shadeInteractor = shadeInteractor,
         shadeSceneViewModel = shadeSceneViewModel,
+        headsUpNotificationInteractor = headsUpNotificationInteractor,
         featureFlags = featureFlagsClassic,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelKosmos.kt
new file mode 100644
index 0000000..ea79edc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelKosmos.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.statusbar.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
+import com.android.systemui.statusbar.policy.batteryController
+
+val Kosmos.keyguardStatusBarViewModel: KeyguardStatusBarViewModel by
+    Kosmos.Fixture {
+        KeyguardStatusBarViewModel(
+            applicationCoroutineScope,
+            headsUpNotificationInteractor,
+            sceneInteractor,
+            keyguardInteractor,
+            keyguardStatusBarInteractor,
+            batteryController,
+        )
+    }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 36d97f6..32491b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -47,6 +47,7 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
@@ -3086,6 +3087,7 @@
         scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
         updateAccessibilityShortcutTargetsLocked(userState, HARDWARE);
         updateAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
+        updateAccessibilityShortcutTargetsLocked(userState, GESTURE);
         updateAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
         // Update the capabilities before the mode because we will check the current mode is
         // invalid or not..
@@ -3207,6 +3209,7 @@
         somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, HARDWARE);
         somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
         somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
+        somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, GESTURE);
         somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
         somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
         somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
@@ -3750,23 +3753,19 @@
             return;
         }
 
-        final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
-        shortcutTypeAndShortcutSetting.add(
-                new Pair<>(HARDWARE,
-                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
-        shortcutTypeAndShortcutSetting.add(
-                new Pair<>(SOFTWARE,
-                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
+        final List<Integer> shortcutTypes = new ArrayList<>(4);
+        shortcutTypes.add(HARDWARE);
+        shortcutTypes.add(SOFTWARE);
         if (android.view.accessibility.Flags.a11yQsShortcut()) {
-            shortcutTypeAndShortcutSetting.add(
-                    new Pair<>(QUICK_SETTINGS,
-                            Settings.Secure.ACCESSIBILITY_QS_TARGETS));
+            shortcutTypes.add(QUICK_SETTINGS);
+        }
+        if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            shortcutTypes.add(GESTURE);
         }
 
         final ComponentName serviceName = service.getComponentName();
-        for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
-            int shortcutType = shortcutTypePair.first;
-            String shortcutSettingName = shortcutTypePair.second;
+        for (Integer shortcutType: shortcutTypes) {
+            String shortcutSettingName = ShortcutUtils.convertToKey(shortcutType);
             if (userState.removeShortcutTargetLocked(shortcutType, serviceName)) {
                 final Set<String> currentTargets = userState.getShortcutTargetsLocked(shortcutType);
                 persistColonDelimitedSetToSettingLocked(
@@ -4068,6 +4067,11 @@
             boolean enable, @UserShortcutType int shortcutTypes,
             @NonNull List<String> shortcutTargets, @UserIdInt int userId) {
         enableShortcutsForTargets_enforcePermission();
+        if ((shortcutTypes & GESTURE) == GESTURE
+                && !android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            throw new IllegalArgumentException(
+                    "GESTURE type shortcuts are disabled by feature flag");
+        }
         for (int shortcutType : USER_SHORTCUT_TYPES) {
             if ((shortcutTypes & shortcutType) == shortcutType) {
                 enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
@@ -5451,6 +5455,9 @@
         private final Uri mAccessibilityButtonTargetsUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
 
+        private final Uri mAccessibilityGestureTargetsUri = Settings.Secure.getUriFor(
+                Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+
         private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS);
 
@@ -5504,6 +5511,8 @@
             contentResolver.registerContentObserver(
                     mAccessibilityButtonTargetsUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
+                    mAccessibilityGestureTargetsUri, false, this, UserHandle.USER_ALL);
+            contentResolver.registerContentObserver(
                     mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
                     mUserInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
@@ -5575,6 +5584,10 @@
                     if (readAccessibilityShortcutTargetsLocked(userState, SOFTWARE)) {
                         onUserStateChangedLocked(userState);
                     }
+                } else if (mAccessibilityGestureTargetsUri.equals(uri)) {
+                    if (readAccessibilityShortcutTargetsLocked(userState, GESTURE)) {
+                        onUserStateChangedLocked(userState);
+                    }
                 } else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
                         || mUserInteractiveUiTimeoutUri.equals(uri)) {
                     readUserRecommendedUiTimeoutSettingsLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 710bae9..7bcbc27 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -27,6 +27,12 @@
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
 
 import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -101,6 +107,7 @@
     private final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
 
     private final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
+    private final ArraySet<String> mAccessibilityGestureTargets = new ArraySet<>();
     private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
 
     /**
@@ -224,6 +231,7 @@
         mTouchExplorationGrantedServices.clear();
         mAccessibilityShortcutKeyTargets.clear();
         mAccessibilityButtonTargets.clear();
+        mAccessibilityGestureTargets.clear();
         mTargetAssignedToAccessibilityButton = null;
         mIsTouchExplorationEnabled = false;
         mServiceHandlesDoubleTap = false;
@@ -524,6 +532,20 @@
         }
     }
 
+    private void dumpShortcutTargets(
+            PrintWriter pw, @UserShortcutType int shortcutType, String name) {
+        pw.append("     ").append(name).append(":{");
+        ArraySet<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+        int size = targets.size();
+        for (int i = 0; i < size; i++) {
+            if (i > 0) {
+                pw.append(", ");
+            }
+            pw.append(targets.valueAt(i));
+        }
+        pw.println("}");
+    }
+
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.append("User state[");
         pw.println();
@@ -553,30 +575,12 @@
                 .append(String.valueOf(mAlwaysOnMagnificationEnabled));
         pw.append("}");
         pw.println();
-        pw.append("     shortcut key:{");
-        int size = mAccessibilityShortcutKeyTargets.size();
-        for (int i = 0; i < size; i++) {
-            final String componentId = mAccessibilityShortcutKeyTargets.valueAt(i);
-            pw.append(componentId);
-            if (i + 1 < size) {
-                pw.append(", ");
-            }
-        }
-        pw.println("}");
-        pw.append("     button:{");
-        size = mAccessibilityButtonTargets.size();
-        for (int i = 0; i < size; i++) {
-            final String componentId = mAccessibilityButtonTargets.valueAt(i);
-            pw.append(componentId);
-            if (i + 1 < size) {
-                pw.append(", ");
-            }
-        }
-        pw.println("}");
+        dumpShortcutTargets(pw, HARDWARE, "shortcut key");
+        dumpShortcutTargets(pw, SOFTWARE, "button");
         pw.append("     button target:{").append(mTargetAssignedToAccessibilityButton);
         pw.println("}");
-        pw.append("     qs shortcut targets:").append(mAccessibilityQsTargets.toString());
-        pw.println();
+        dumpShortcutTargets(pw, GESTURE, "gesture");
+        dumpShortcutTargets(pw, QUICK_SETTINGS, "qs shortcut targets");
         pw.append("     a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
         pw.println();
         pw.append("     Bound services:{");
@@ -782,15 +786,17 @@
     }
 
     private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) {
-        if (shortcutType == UserShortcutType.HARDWARE) {
+        if (shortcutType == HARDWARE) {
             return mAccessibilityShortcutKeyTargets;
-        } else if (shortcutType == UserShortcutType.SOFTWARE) {
+        } else if (shortcutType == SOFTWARE) {
             return mAccessibilityButtonTargets;
-        } else if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+        } else if (shortcutType == GESTURE) {
+            return mAccessibilityGestureTargets;
+        } else if (shortcutType == QUICK_SETTINGS) {
             return mAccessibilityQsTargets;
-        } else if ((shortcutType == UserShortcutType.TRIPLETAP
+        } else if ((shortcutType == TRIPLETAP
                 && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
-                shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP
+                shortcutType == TWOFINGER_DOUBLETAP
                         && isMagnificationTwoFingerTripleTapEnabledLocked())) {
             ArraySet<String> targets = new ArraySet<>();
             targets.add(MAGNIFICATION_CONTROLLER_NAME);
@@ -811,7 +817,7 @@
      */
     boolean updateShortcutTargetsLocked(
             Set<String> newTargets, @UserShortcutType int shortcutType) {
-        final int mask = UserShortcutType.TRIPLETAP | UserShortcutType.TWOFINGER_DOUBLETAP;
+        final int mask = TRIPLETAP | TWOFINGER_DOUBLETAP;
         if ((shortcutType & mask) != 0) {
             throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets.");
         }
@@ -867,8 +873,8 @@
      */
     public boolean removeShortcutTargetLocked(
             @UserShortcutType int shortcutType, ComponentName target) {
-        if (shortcutType == UserShortcutType.TRIPLETAP
-                || shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
+        if (shortcutType == TRIPLETAP
+                || shortcutType == TWOFINGER_DOUBLETAP) {
             throw new UnsupportedOperationException(
                     "removeShortcutTargetLocked only support shortcut type: "
                             + "software and hardware and quick settings for now"
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f4b1229..779aabe 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5559,8 +5559,9 @@
     void noteAppKill(final ProcessRecord app, final @Reason int reason,
             final @SubReason int subReason, final String msg) {
         if (DEBUG_PROCESSES) {
-            Slog.i(TAG, "note: " + app + " is being killed, reason: " + reason
-                    + ", sub-reason: " + subReason + ", message: " + msg);
+            Slog.i(TAG, "note: " + app + " is being killed, reason: "
+                    + ApplicationExitInfo.reasonCodeToString(reason) + ", sub-reason: "
+                    + ApplicationExitInfo.subreasonToString(subReason) + ", message: " + msg);
         }
         if (app.getPid() > 0 && !app.isolated && app.getDeathRecipient() != null) {
             // We are killing it, put it into the dying process list.
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index cbea7aa..932b9c0 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -136,6 +136,7 @@
     static final String[] sDeviceConfigAconfigScopes = new String[] {
         "accessibility",
         "android_core_networking",
+        "android_sdk",
         "android_stylus",
         "aoc",
         "app_widgets",
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index b1a12f7..5d83ad6 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.FILTER_BY_UID;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
 import static android.app.AppOpsManager.OP_FINE_LOCATION;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
@@ -135,9 +136,10 @@
     private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
     private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
     private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
-            + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
-            + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + ","
-            + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + "," + OP_RESERVED_FOR_TESTING;
+            + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
+            + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+            + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+            + "," + OP_RESERVED_FOR_TESTING;
     private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
     private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
     private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9732621..989a4d1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -708,10 +708,14 @@
     // Streams currently muted by ringer mode and dnd
     protected static volatile int sRingerAndZenModeMutedStreams;
 
-    /** Streams that can be muted. Do not resolve to aliases when checking.
+    /** Streams that can be muted by system. Do not resolve to aliases when checking.
      * @see System#MUTE_STREAMS_AFFECTED */
     private int mMuteAffectedStreams;
 
+    /** Streams that can be muted by user. Do not resolve to aliases when checking.
+     * @see System#MUTE_STREAMS_AFFECTED */
+    private int mUserMutableStreams;
+
     @NonNull
     private SoundEffectsHelper mSfxHelper;
 
@@ -2346,6 +2350,7 @@
                 mMuteAffectedStreams &= ~(1 << vss.mStreamType);
             }
         }
+        updateUserMutableStreams();
     }
 
     private void createStreamStates() {
@@ -2415,6 +2420,8 @@
         }
         pw.print("\n- mute affected streams = 0x");
         pw.println(Integer.toHexString(mMuteAffectedStreams));
+        pw.print("\n- user mutable streams = 0x");
+        pw.println(Integer.toHexString(mUserMutableStreams));
     }
 
     private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
@@ -2909,6 +2916,7 @@
         mMuteAffectedStreams = mSettings.getSystemIntForUser(cr,
                 System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED,
                 UserHandle.USER_CURRENT);
+        updateUserMutableStreams();
 
         updateMasterMono(cr);
 
@@ -2928,6 +2936,12 @@
         mVolumeController.loadSettings(cr);
     }
 
+    private void updateUserMutableStreams() {
+        mUserMutableStreams = mMuteAffectedStreams;
+        mUserMutableStreams &= ~(1 << AudioSystem.STREAM_VOICE_CALL);
+        mUserMutableStreams &= ~(1 << AudioSystem.STREAM_BLUETOOTH_SCO);
+    }
+
     @GuardedBy("mSettingsLock")
     private void resetActiveAssistantUidsLocked() {
         mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS;
@@ -7142,6 +7156,11 @@
         return (mMuteAffectedStreams & (1 << streamType)) != 0;
     }
 
+    @Override
+    public boolean isStreamMutableByUi(int streamType) {
+        return (mUserMutableStreams & (1 << streamType)) != 0;
+    }
+
     private void ensureValidDirection(int direction) {
         switch (direction) {
             case AudioManager.ADJUST_LOWER:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 275c930..0f9c344 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -67,7 +67,7 @@
 /**
  * Represent a logical device of type TV residing in Android system.
  */
-public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
+public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     private static final String TAG = "HdmiCecLocalDeviceTv";
 
     // Whether ARC is available or not. "true" means that ARC is established between TV and
@@ -113,6 +113,18 @@
     // handle.
     private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
 
+    private boolean mWasActiveSourceSetToConnectedDevice = false;
+
+    @VisibleForTesting
+    protected boolean getWasActiveSourceSetToConnectedDevice() {
+        return mWasActiveSourceSetToConnectedDevice;
+    }
+
+    protected void setWasActiveSourceSetToConnectedDevice(
+            boolean wasActiveSourceSetToConnectedDevice) {
+        mWasActiveSourceSetToConnectedDevice = wasActiveSourceSetToConnectedDevice;
+    }
+
     // Defines the callback invoked when TV input framework is updated with input status.
     // We are interested in the notification for HDMI input addition event, in order to
     // process any CEC commands that arrived before the input is added.
@@ -474,6 +486,7 @@
                 || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
             mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
                     HdmiControlManager.POWER_STATUS_ON);
+            setWasActiveSourceSetToConnectedDevice(true);
             ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
             ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
         } else {
@@ -489,13 +502,16 @@
     protected int handleStandby(HdmiCecMessage message) {
         assertRunOnServiceThread();
 
-        // Ignore <Standby> from non-active source device.
-        if (getActiveSource().logicalAddress != message.getSource()) {
+        // If a device has previously asserted the active source status, ignore <Standby> from
+        // non-active source.
+        if (getWasActiveSourceSetToConnectedDevice()
+                && getActiveSource().logicalAddress != message.getSource()) {
             Slog.d(TAG, "<Standby> was not sent by the current active source, ignoring."
                     + " Current active source has logical address "
                     + getActiveSource().logicalAddress);
             return Constants.HANDLED;
         }
+        setWasActiveSourceSetToConnectedDevice(false);
         return super.handleStandby(message);
     }
 
@@ -1457,6 +1473,7 @@
             invokeStandbyCompletedCallback(callback);
             return;
         }
+        setWasActiveSourceSetToConnectedDevice(false);
         boolean sendStandbyOnSleep =
                 mService.getHdmiCecConfig().getIntValue(
                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e5dbce9..bef984b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -53,6 +53,7 @@
 import android.hardware.input.InputManager;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGlyphMap;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.hardware.input.TouchCalibration;
@@ -311,6 +312,9 @@
     // Manages Keyboard modifier keys remapping
     private final KeyRemapper mKeyRemapper;
 
+    // Manages Keyboard glyphs for specific keyboards
+    private final KeyboardGlyphManager mKeyboardGlyphManager;
+
     // Manages loading PointerIcons
     private final PointerIconCache mPointerIconCache;
 
@@ -460,6 +464,7 @@
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
                 mNative);
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
+        mKeyboardGlyphManager = new KeyboardGlyphManager(mContext, injector.getLooper());
         mPointerIconCache = new PointerIconCache(mContext, mNative);
 
         mUseDevInputEventForAudioJack =
@@ -576,6 +581,7 @@
         mKeyboardLedController.systemRunning();
         mKeyRemapper.systemRunning();
         mPointerIconCache.systemRunning();
+        mKeyboardGlyphManager.systemRunning();
     }
 
     private void reloadDeviceAliases() {
@@ -1208,6 +1214,11 @@
                 imeInfo, imeSubtype);
     }
 
+    @Override // Binder call
+    public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+        return mKeyboardGlyphManager.getKeyGlyphMap(deviceId);
+    }
+
     public void setFocusedApplication(int displayId, InputApplicationHandle application) {
         mNative.setFocusedApplication(displayId, application);
     }
@@ -2077,6 +2088,7 @@
         mBatteryController.dump(ipw);
         mKeyboardBacklightController.dump(ipw);
         mKeyboardLedController.dump(ipw);
+        mKeyboardGlyphManager.dump(ipw);
     }
 
     private void dumpAssociations(IndentingPrintWriter pw) {
diff --git a/services/core/java/com/android/server/input/KeyboardGlyphManager.java b/services/core/java/com/android/server/input/KeyboardGlyphManager.java
new file mode 100644
index 0000000..f59d72b
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardGlyphManager.java
@@ -0,0 +1,382 @@
+/*
+ * 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 static com.android.hardware.input.Flags.keyboardGlyphMap;
+
+import android.annotation.AnyRes;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyGlyphMap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.InputDevice;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides information on custom glyphs configured for specific keyboard devices.
+ *
+ * @hide
+ */
+public final class KeyboardGlyphManager implements InputManager.InputDeviceListener {
+
+    private static final String TAG = "KeyboardGlyphManager";
+    // To enable these logs, run: 'adb shell setprop log.tag.KeyboardGlyphManager DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String TAG_KEYBOARD_GLYPH_MAPS = "keyboard-glyph-maps";
+    private static final String TAG_KEYBOARD_GLYPH_MAP = "keyboard-glyph-map";
+    private static final String TAG_KEY_GLYPH = "key-glyph";
+    private static final String TAG_MODIFIER_GLYPH = "modifier-glyph";
+    private static final String TAG_FUNCTION_ROW_KEY = "function-row-key";
+    private static final String TAG_HARDWARE_DEFINED_SHORTCUT = "hardware-defined-shortcut";
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Object mGlyphMapLock = new Object();
+    @GuardedBy("mGlyphMapLock")
+    private boolean mGlyphMapDataLoaded = false;
+    @GuardedBy("mGlyphMapLock")
+    private List<KeyGlyphMapData> mGlyphMapDataList = new ArrayList<>();
+    // Cache for already loaded glyph maps
+    @GuardedBy("mGlyphMapLock")
+    private final SparseArray<KeyGlyphMap> mGlyphMapCache = new SparseArray<>();
+
+    KeyboardGlyphManager(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new Handler(looper);
+    }
+
+    void systemRunning() {
+        if (!keyboardGlyphMap()) {
+            return;
+        }
+        // Listen to new Package installations to fetch new Keyboard glyph maps
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                resetMaps();
+            }
+        }, filter, null, mHandler);
+    }
+
+    @Override
+    @MainThread
+    public void onInputDeviceAdded(int deviceId) {
+    }
+
+    @Override
+    @MainThread
+    public void onInputDeviceRemoved(int deviceId) {
+        synchronized (mGlyphMapLock) {
+            mGlyphMapCache.remove(deviceId);
+        }
+    }
+
+    @Override
+    @MainThread
+    public void onInputDeviceChanged(int deviceId) {
+    }
+
+    @MainThread
+    private void resetMaps() {
+        synchronized (mGlyphMapLock) {
+            mGlyphMapDataLoaded = false;
+            mGlyphMapDataList.clear();
+            mGlyphMapCache.clear();
+        }
+    }
+
+    @NonNull
+    private List<KeyGlyphMapData> loadGlyphMapDataList() {
+        final PackageManager pm = mContext.getPackageManager();
+        List<KeyGlyphMapData> glyphMaps = new ArrayList<>();
+        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_GLYPH_MAPS);
+        for (ResolveInfo resolveInfo : pm.queryBroadcastReceiversAsUser(intent,
+                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.USER_SYSTEM)) {
+            if (resolveInfo == null || resolveInfo.activityInfo == null) {
+                continue;
+            }
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            KeyGlyphMapData data = getKeyboardGlyphMapsInPackage(pm, activityInfo);
+            if (data == null) {
+                continue;
+            }
+            glyphMaps.add(data);
+        }
+        return glyphMaps;
+    }
+
+    @Nullable
+    private KeyGlyphMapData getKeyboardGlyphMapsInPackage(PackageManager pm,
+            @NonNull ActivityInfo receiver) {
+        Bundle metaData = receiver.metaData;
+        if (metaData == null) {
+            return null;
+        }
+
+        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_GLYPH_MAPS);
+        if (configResId == 0) {
+            Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_GLYPH_MAPS
+                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
+            return null;
+        }
+
+        try {
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            try (XmlResourceParser parser = resources.getXml(configResId)) {
+                XmlUtils.beginDocument(parser, TAG_KEYBOARD_GLYPH_MAPS);
+
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (!TAG_KEYBOARD_GLYPH_MAP.equals(element)) {
+                        continue;
+                    }
+                    TypedArray a = resources.obtainAttributes(parser, R.styleable.KeyboardGlyphMap);
+                    try {
+                        int glyphMapRes = a.getResourceId(R.styleable.KeyboardGlyphMap_glyphMap, 0);
+                        int vendor = a.getInt(R.styleable.KeyboardGlyphMap_vendorId, -1);
+                        int product = a.getInt(R.styleable.KeyboardGlyphMap_productId, -1);
+                        if (glyphMapRes != 0 && vendor != -1 && product != -1) {
+                            return new KeyGlyphMapData(receiver.packageName, receiver.name,
+                                    glyphMapRes, vendor, product);
+                        }
+                    } finally {
+                        a.recycle();
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            Slog.w(TAG, "Could not parse keyboard glyph map resource from receiver "
+                    + receiver.packageName + "/" + receiver.name, ex);
+        }
+        return null;
+    }
+
+    @Nullable
+    private KeyGlyphMap loadGlyphMap(KeyGlyphMapData data) {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            ComponentName componentName = new ComponentName(data.packageName, data.receiverName);
+            ActivityInfo receiver = pm.getReceiverInfo(componentName,
+                    PackageManager.GET_META_DATA
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            return loadGlyphMapFromResource(resources, componentName, data.resourceId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Package not found: " + e);
+        }
+        return null;
+    }
+
+    @NonNull
+    private KeyGlyphMap loadGlyphMapFromResource(Resources resources,
+            @NonNull ComponentName componentName, @AnyRes int glyphMapId) {
+        SparseIntArray keyGlyphs = new SparseIntArray();
+        SparseIntArray modifierGlyphs = new SparseIntArray();
+        List<Integer> functionRowKeys = new ArrayList<>();
+        HashMap<KeyGlyphMap.KeyCombination, Integer> hardwareShortcuts = new HashMap<>();
+        try {
+            XmlResourceParser parser = resources.getXml(glyphMapId);
+            XmlUtils.beginDocument(parser, TAG_KEYBOARD_GLYPH_MAP);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+                String element = parser.getName();
+                if (element == null) {
+                    break;
+                }
+                switch (element) {
+                    case TAG_KEY_GLYPH -> {
+                        final TypedArray a = resources.obtainAttributes(parser,
+                                R.styleable.KeyGlyph);
+                        try {
+                            int keycode = a.getInt(R.styleable.KeyGlyph_keycode, 0);
+                            int keyGlyph = a.getResourceId(R.styleable.KeyGlyph_glyphDrawable, 0);
+                            if (keycode != 0 && keyGlyph != 0) {
+                                keyGlyphs.put(keycode, keyGlyph);
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    }
+                    case TAG_MODIFIER_GLYPH -> {
+                        final TypedArray a = resources.obtainAttributes(parser,
+                                R.styleable.ModifierGlyph);
+                        try {
+                            int modifier = a.getInt(R.styleable.ModifierGlyph_modifier, 0);
+                            int modifierGlyph = a.getResourceId(
+                                    R.styleable.ModifierGlyph_glyphDrawable,
+                                    0);
+                            if (modifier != 0 && modifierGlyph != 0) {
+                                modifierGlyphs.put(modifier, modifierGlyph);
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    }
+                    case TAG_FUNCTION_ROW_KEY -> {
+                        final TypedArray a = resources.obtainAttributes(parser,
+                                R.styleable.FunctionRowKey);
+                        try {
+                            int keycode = a.getInt(R.styleable.FunctionRowKey_keycode, 0);
+                            if (keycode != 0) {
+                                functionRowKeys.add(keycode);
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    }
+                    case TAG_HARDWARE_DEFINED_SHORTCUT -> {
+                        final TypedArray a = resources.obtainAttributes(parser,
+                                R.styleable.HardwareDefinedShortcut);
+                        try {
+                            int keycode = a.getInt(R.styleable.HardwareDefinedShortcut_keycode,
+                                    0);
+                            int modifierState = a.getInt(
+                                    R.styleable.HardwareDefinedShortcut_modifierState, 0);
+                            int outKeycode = a.getInt(
+                                    R.styleable.HardwareDefinedShortcut_outKeycode,
+                                    0);
+                            if (keycode != 0 && modifierState != 0 && outKeycode != 0) {
+                                hardwareShortcuts.put(
+                                        new KeyGlyphMap.KeyCombination(modifierState, keycode),
+                                        outKeycode);
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    }
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Log.e(TAG, "Unable to parse key glyph map : " + e);
+        }
+        return new KeyGlyphMap(componentName, keyGlyphs, modifierGlyphs,
+                functionRowKeys.stream().mapToInt(Integer::intValue).toArray(), hardwareShortcuts);
+    }
+
+    /**
+     * Returns keyboard glyph map corresponding to device ID
+     */
+    @Nullable
+    public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+        if (!keyboardGlyphMap()) {
+            return null;
+        }
+        synchronized (mGlyphMapLock) {
+            if (mGlyphMapCache.indexOfKey(deviceId) >= 0) {
+                return mGlyphMapCache.get(deviceId);
+            }
+            KeyGlyphMap keyGlyphMap = getKeyGlyphMapInternal(deviceId);
+            mGlyphMapCache.put(deviceId, keyGlyphMap);
+            return keyGlyphMap;
+        }
+    }
+
+    @GuardedBy("mGlyphMapLock")
+    private KeyGlyphMap getKeyGlyphMapInternal(int deviceId) {
+        final InputDevice inputDevice = getInputDevice(deviceId);
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return null;
+        }
+        if (!mGlyphMapDataLoaded) {
+            mGlyphMapDataList = loadGlyphMapDataList();
+            mGlyphMapDataLoaded = true;
+        }
+        for (KeyGlyphMapData data : mGlyphMapDataList) {
+            if (data.vendorId == inputDevice.getVendorId()
+                    && data.productId == inputDevice.getProductId()) {
+                return loadGlyphMap(data);
+            }
+        }
+        return null;
+    }
+
+    void dump(IndentingPrintWriter ipw) {
+        if (!keyboardGlyphMap()) {
+            return;
+        }
+        List<KeyGlyphMapData> glyphMapDataList = loadGlyphMapDataList();
+        ipw.println(TAG + ": " + glyphMapDataList.size() + " glyph maps");
+        ipw.increaseIndent();
+        for (KeyGlyphMapData data : glyphMapDataList) {
+            ipw.println(data);
+            if (DEBUG) {
+                KeyGlyphMap map = loadGlyphMap(data);
+                if (map != null) {
+                    ipw.increaseIndent();
+                    ipw.println(map);
+                    ipw.decreaseIndent();
+                }
+            }
+        }
+        ipw.decreaseIndent();
+    }
+
+    @Nullable
+    private InputDevice getInputDevice(int deviceId) {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
+    }
+
+    private record KeyGlyphMapData(@NonNull String packageName, @NonNull String receiverName,
+                                   @AnyRes int resourceId, int vendorId, int productId) {
+    }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 97c32b9..4993412 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -255,17 +255,6 @@
         }
     }
 
-    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
-        // Different languages are never compatible
-        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
-            return false;
-        }
-        // If both the system and the keyboard layout have a country specifier, they must be equal.
-        return TextUtils.isEmpty(systemLocale.getCountry())
-                || TextUtils.isEmpty(keyboardLocale.getCountry())
-                || systemLocale.getCountry().equals(keyboardLocale.getCountry());
-    }
-
     @MainThread
     private void updateKeyboardLayouts() {
         // Scan all input devices state for keyboard layouts that have been uninstalled.
@@ -953,21 +942,33 @@
             return;
         }
 
+        List<String> layoutNames = new ArrayList<>();
+        for (String layoutDesc : config.getConfiguredLayouts()) {
+            KeyboardLayout kl = getKeyboardLayout(layoutDesc);
+            if (kl == null) {
+                // b/349033234: Weird state with stale keyboard layout configured.
+                // Possibly due to race condition between KCM providing package being removed and
+                // corresponding layouts being removed from Datastore and cache.
+                // {@see updateKeyboardLayouts()}
+                //
+                // Ideally notification will be correctly shown after the keyboard layouts are
+                // configured again with the new package state.
+                return;
+            }
+            layoutNames.add(kl.getLabel());
+        }
         showKeyboardLayoutNotification(
                 r.getString(
                         R.string.keyboard_layout_notification_selected_title,
                         inputDevice.getName()),
-                createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
+                createConfiguredNotificationText(mContext, layoutNames),
                 inputDevice);
     }
 
     @MainThread
     private String createConfiguredNotificationText(@NonNull Context context,
-            @NonNull Set<String> selectedLayouts) {
+            @NonNull List<String> layoutNames) {
         final Resources r = context.getResources();
-        List<String> layoutNames = new ArrayList<>();
-        selectedLayouts.forEach(
-                (layoutDesc) -> layoutNames.add(getKeyboardLayout(layoutDesc).getLabel()));
         Collections.sort(layoutNames);
         switch (layoutNames.size()) {
             case 1:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index e1aa3a2..5ab493b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -96,6 +96,32 @@
     @GuardedBy("ImfLock.class") private int mDisplayIdToShowIme = INVALID_DISPLAY;
     @GuardedBy("ImfLock.class") private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
 
+    /**
+     * A set of status bits regarding the active IME.
+     *
+     * <p>This value is a combination of following two bits:</p>
+     * <dl>
+     * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
+     * <dd>
+     *   If this bit is ON, connected IME is ready to accept touch/key events.
+     * </dd>
+     * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
+     * <dd>
+     *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
+     * </dd>
+     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
+     *    currently invisible.
+     * </dd>
+     * </dl>
+     * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
+     * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
+     */
+    @GuardedBy("ImfLock.class") private int mImeWindowVis;
+
+    @GuardedBy("ImfLock.class")
+    private int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+
     @Nullable private CountDownLatch mLatchForTesting;
 
     /**
@@ -473,7 +499,7 @@
 
         if (getCurToken() != null) {
             removeCurrentToken();
-            mService.resetSystemUiLocked();
+            mService.resetSystemUiLocked(this);
             mAutofillController.onResetSystemUi();
         }
 
@@ -638,6 +664,36 @@
         }
     }
 
+    /**
+     * Returns the current {@link InputMethodSubtype}.
+     *
+     * <p>Also this method has had questionable behaviors:</p>
+     * <ul>
+     *     <li>Calling this method can update {@link #mCurrentSubtype}.</li>
+     *     <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong to
+     *     the current IME.</li>
+     * </ul>
+     * <p>TODO(b/347083680): Address above issues.</p>
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    InputMethodSubtype getCurrentInputMethodSubtype() {
+        final var selectedMethodId = getSelectedMethodId();
+        if (selectedMethodId == null) {
+            return null;
+        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mUserId);
+        final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
+        if (imi == null || imi.getSubtypeCount() == 0) {
+            return null;
+        }
+        final var subtype = SubtypeUtils.getCurrentInputMethodSubtype(imi, settings,
+                mCurrentSubtype);
+        mCurrentSubtype = subtype;
+        return subtype;
+    }
+
+
     @GuardedBy("ImfLock.class")
     void setDisplayIdToShowIme(int displayId) {
         mDisplayIdToShowIme = displayId;
@@ -662,4 +718,24 @@
     int getUserId() {
         return mUserId;
     }
+
+    @GuardedBy("ImfLock.class")
+    void setImeWindowVis(int imeWindowVis) {
+        mImeWindowVis = imeWindowVis;
+    }
+
+    @GuardedBy("ImfLock.class")
+    int getImeWindowVis() {
+        return mImeWindowVis;
+    }
+
+    @GuardedBy("ImfLock.class")
+    int getBackDisposition() {
+        return mBackDisposition;
+    }
+
+    @GuardedBy("ImfLock.class")
+    void setBackDisposition(int backDisposition) {
+        mBackDisposition = backDisposition;
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 78d501c..9c3b9d5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -552,33 +552,6 @@
     @MultiUserUnawareField
     boolean mIsInteractive = true;
 
-    @MultiUserUnawareField
-    int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
-
-    /**
-     * A set of status bits regarding the active IME.
-     *
-     * <p>This value is a combination of following two bits:</p>
-     * <dl>
-     * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
-     * <dd>
-     *   If this bit is ON, connected IME is ready to accept touch/key events.
-     * </dd>
-     * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
-     * <dd>
-     *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
-     * </dd>
-     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
-     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
-     *    currently invisible.
-     * </dd>
-     * </dl>
-     * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
-     * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
-     */
-    @MultiUserUnawareField
-    int mImeWindowVis;
-
     @SharedByAllUsersField
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
 
@@ -936,7 +909,9 @@
                             // Uh oh, current input method is no longer around!
                             // Pick another one...
                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
-                            updateSystemUiLocked(0 /* vis */, mBackDisposition);
+                            final var bindingController = getInputMethodBindingController(userId);
+                            updateSystemUiLocked(0 /* vis */,
+                                    bindingController.getBackDisposition(), userId);
                             if (!chooseNewDefaultIMELocked()) {
                                 changed = true;
                                 curIm = null;
@@ -1412,7 +1387,9 @@
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
-                updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+                final var bindingController = getInputMethodBindingController(currentUserId);
+                updateSystemUiLocked(bindingController.getImeWindowVis(),
+                        bindingController.getBackDisposition(), currentUserId);
                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
                 if (mShowOngoingImeSwitcherForPhones) {
@@ -2426,11 +2403,13 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void resetSystemUiLocked() {
+    void resetSystemUiLocked(InputMethodBindingController bindingController) {
         // Set IME window status as invisible when unbinding current method.
-        mImeWindowVis = 0;
-        mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
-        updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+        final int imeWindowVis = 0;
+        final int backDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+        bindingController.setImeWindowVis(imeWindowVis);
+        bindingController.setBackDisposition(backDisposition);
+        updateSystemUiLocked(imeWindowVis, backDisposition, bindingController.getUserId());
     }
 
     @GuardedBy("ImfLock.class")
@@ -2544,7 +2523,12 @@
                     sessionState.mSession.finishSession();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Session failed to close due to remote exception", e);
-                    updateSystemUiLocked(0 /* vis */, mBackDisposition);
+                    // TODO(b/350386877): Propagate userId from the caller or infer it from
+                    //  sessionState
+                    final int userId = mCurrentUserId;
+                    final var bindingController = getInputMethodBindingController(userId);
+                    updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(),
+                            userId);
                 }
                 sessionState.mSession = null;
             }
@@ -2764,8 +2748,8 @@
             if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
                 return;
             }
-            mImeWindowVis = vis;
-            mBackDisposition = backDisposition;
+            bindingController.setImeWindowVis(vis);
+            bindingController.setBackDisposition(backDisposition);
             updateSystemUiLocked(vis, backDisposition, userId);
         }
 
@@ -2802,23 +2786,23 @@
 
     private void updateImeWindowStatus(boolean disableImeIcon) {
         synchronized (ImfLock.class) {
+            // TODO(b/350386877): Propagate userId from the caller.
+            final int userId = mCurrentUserId;
             if (disableImeIcon) {
-                updateSystemUiLocked(0, mBackDisposition);
+                final var bindingController = getInputMethodBindingController(userId);
+                updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
             } else {
-                updateSystemUiLocked();
+                updateSystemUiLocked(userId);
             }
         }
     }
 
-    @GuardedBy("ImfLock.class")
-    void updateSystemUiLocked() {
-        updateSystemUiLocked(mImeWindowVis, mBackDisposition);
-    }
-
     // Caution! This method is called in this class. Handle multi-user carefully
     @GuardedBy("ImfLock.class")
-    private void updateSystemUiLocked(int vis, int backDisposition) {
-        updateSystemUiLocked(vis, backDisposition, mCurrentUserId);
+    void updateSystemUiLocked(@UserIdInt int userId) {
+        final var bindingController = getInputMethodBindingController(userId);
+        updateSystemUiLocked(bindingController.getImeWindowVis(),
+                bindingController.getBackDisposition(), userId);
     }
 
     @GuardedBy("ImfLock.class")
@@ -3054,7 +3038,7 @@
                 // getCurrentInputMethodSubtype.
                 subtypeId = NOT_A_SUBTYPE_ID;
                 // TODO(b/347083680): The method below has questionable behaviors.
-                newSubtype = getCurrentInputMethodSubtypeLocked();
+                newSubtype = bindingController.getCurrentInputMethodSubtype();
                 if (newSubtype != null) {
                     for (int i = 0; i < subtypeCount; ++i) {
                         if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
@@ -3068,7 +3052,8 @@
                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId);
                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
                 if (curMethod != null) {
-                    updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+                    updateSystemUiLocked(bindingController.getImeWindowVis(),
+                            bindingController.getBackDisposition(), userId);
                     curMethod.changeInputMethodSubtype(newSubtype);
                 }
             }
@@ -3455,7 +3440,7 @@
                     return;
                 }
                 mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
-                updateSystemUiLocked();
+                updateSystemUiLocked(mCurrentUserId);
             }
         });
     }
@@ -3621,29 +3606,30 @@
     boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
             @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
+        final var bindingController = getInputMethodBindingController(userId);
         if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
             return false;
         }
 
         // There is a chance that IMM#hideSoftInput() is called in a transient state where
-        // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
-        // to be updated with the new value sent from IME process.  Even in such a transient state
-        // historically we have accepted an incoming call of IMM#hideSoftInput() from the
+        // IMMS#InputShown is already updated to be true whereas the user's ImeWindowVis is still
+        // waiting to be updated with the new value sent from IME process.  Even in such a transient
+        // state historically we have accepted an incoming call of IMM#hideSoftInput() from the
         // application process as a valid request, and have even promised such a behavior with CTS
         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO(b/246309664): Clean up IMMS#mImeWindowVis
-        final var bindingController = getInputMethodBindingController(userId);
         final var userData = getUserData(userId);
         IInputMethodInvoker curMethod = bindingController.getCurMethod();
         final boolean shouldHideSoftInput = curMethod != null
-                && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+                && (isInputShownLocked()
+                || (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
 
         mVisibilityStateComputer.requestImeVisibility(windowToken, false);
         if (shouldHideSoftInput) {
             // The IME will report its visible state again after the following message finally
             // delivered to the IME process as an IPC.  Hence the inconsistency between
-            // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
+            // IMMS#mInputShown and the user's ImeWindowVis should be resolved spontaneously in
             // the final state.
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason,
@@ -4633,8 +4619,8 @@
             proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
             proto.write(BOUND_TO_METHOD, userData.mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
-            proto.write(BACK_DISPOSITION, mBackDisposition);
-            proto.write(IME_WINDOW_VISIBILITY, mImeWindowVis);
+            proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
+            proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
             proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
             proto.end(token);
         }
@@ -5143,18 +5129,18 @@
 
     private void handleSetInteractive(final boolean interactive) {
         synchronized (ImfLock.class) {
-            mIsInteractive = interactive;
-            updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
-
             // TODO(b/305849394): Support multiple IMEs.
-            final var userId = mCurrentUserId;
+            final int userId = mCurrentUserId;
+            final var bindingController = getInputMethodBindingController(userId);
+            mIsInteractive = interactive;
+            updateSystemUiLocked(
+                    interactive ? bindingController.getImeWindowVis() : 0,
+                    bindingController.getBackDisposition(), userId);
             final var userData = getUserData(userId);
             // Inform the current client of the change in active status
             if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
                 return;
             }
-            // TODO(b/325515685): user data must be retrieved by a userId parameter
-            final var bindingController = getInputMethodBindingController(mCurrentUserId);
             if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
                     bindingController.getCurMethodUid())) {
                 // Handle IME visibility when interactive changed before finishing the input to
@@ -5500,7 +5486,7 @@
                 newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
                 // If the subtype is not specified, choose the most applicable one
                 // TODO(b/347083680): The method below has questionable behaviors.
-                newSubtype = getCurrentInputMethodSubtypeLocked();
+                newSubtype = bindingController.getCurrentInputMethodSubtype();
             }
         }
         settings.putSelectedSubtype(newSubtypeHashcode);
@@ -5555,54 +5541,13 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mCurrentUserId == userId) {
-                // TODO(b/347083680): The method below has questionable behaviors.
-                return getCurrentInputMethodSubtypeLocked();
-            }
-
-            return InputMethodSettingsRepository.get(userId)
-                    .getCurrentInputMethodSubtypeForNonCurrentUsers();
+            final var bindingController = getInputMethodBindingController(userId);
+            // TODO(b/347083680): The method below has questionable behaviors.
+            return bindingController.getCurrentInputMethodSubtype();
         }
     }
 
     /**
-     * Returns the current {@link InputMethodSubtype} for the current user.
-     *
-     * <p>CAVEATS: You must also update
-     * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}
-     * when you update the algorithm of this method.</p>
-     *
-     * <p>TODO: Address code duplication between this and
-     * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.</p>
-     *
-     * <p>Also this method has had questionable behaviors:</p>
-     * <ul>
-     *     <li>Calling this method can update {@link #mCurrentSubtype}.</li>
-     *     <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong
-     *         to the current IME.</li>
-     * </ul>
-     * <p>TODO(b/347083680): Address above issues.</p>
-     */
-    @GuardedBy("ImfLock.class")
-    InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
-        final int userId = mCurrentUserId;
-        final var selectedMethodId = getInputMethodBindingController(userId).getSelectedMethodId();
-        if (selectedMethodId == null) {
-            return null;
-        }
-        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
-        if (imi == null || imi.getSubtypeCount() == 0) {
-            return null;
-        }
-        final var bindingController = getInputMethodBindingController(userId);
-        final var subtype = SubtypeUtils.getCurrentInputMethodSubtype(imi, settings,
-                bindingController.getCurrentSubtype());
-        bindingController.setCurrentSubtype(subtype);
-        return subtype;
-    }
-
-    /**
      * Returns the default {@link InputMethodInfo} for the specific userId.
      *
      * @param userId user ID to query
@@ -6173,7 +6118,19 @@
                         p.println("      hasMainConnection="
                                 + u.mBindingController.hasMainConnection());
                         p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
-                        p.println("      mSwitchingController:");
+                        p.println("      boundToMethod=" + u.mBoundToMethod);
+                        p.println("      curClient=" + u.mCurClient);
+                        if (u.mCurEditorInfo != null) {
+                            p.println("      curEditorInfo:");
+                            u.mCurEditorInfo.dump(p, "        ", false /* dumpExtras */);
+                        } else {
+                            p.println("      curEditorInfo: null");
+                        }
+                        p.println("      imeBindingState:");
+                        u.mImeBindingState.dump("        ", p);
+                        p.println("      enabledSession=" + u.mEnabledSession);
+                        p.println("      inFullscreenMode=" + u.mInFullscreenMode);
+                        p.println("      switchingController:");
                         u.mSwitchingController.dump(p, "        ");
                     };
             mUserDataRepository.forAllUserData(userDataDump);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 89a31e7..656c87d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -85,7 +85,7 @@
 
         if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
             final InputMethodSubtype currentSubtype =
-                    mService.getCurrentInputMethodSubtypeLocked();
+                    bindingController.getCurrentInputMethodSubtype();
             if (currentSubtype != null) {
                 final String curMethodId = bindingController.getSelectedMethodId();
                 final InputMethodInfo currentImi =
@@ -201,7 +201,7 @@
         attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         attrs.setTitle("Select input method");
         w.setAttributes(attrs);
-        mService.updateSystemUiLocked();
+        mService.updateSystemUiLocked(userId);
         mService.sendOnNavButtonFlagsChangedLocked();
         mSwitchingDialog.show();
     }
@@ -239,7 +239,9 @@
             mSwitchingDialog = null;
             mSwitchingDialogTitleView = null;
 
-            mService.updateSystemUiLocked();
+            // TODO(b/305849394): Make InputMethodMenuController multi-user aware
+            final int userId = mService.getCurrentImeUserIdLocked();
+            mService.updateSystemUiLocked(userId);
             mService.sendOnNavButtonFlagsChangedLocked();
             mDialogBuilder = null;
             mIms = null;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 5569e1e..0152158 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -571,57 +571,6 @@
         }
     }
 
-    /**
-     * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
-     * non-current users.
-     *
-     * <p>TODO: Address code duplication between this and
-     * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
-     *
-     * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
-     */
-    @Nullable
-    InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
-        final String selectedMethodId = getSelectedInputMethod();
-        if (selectedMethodId == null) {
-            return null;
-        }
-        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
-        if (imi == null || imi.getSubtypeCount() == 0) {
-            return null;
-        }
-
-        final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-        if (subtypeHashCode != INVALID_SUBTYPE_HASHCODE) {
-            final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
-                    subtypeHashCode);
-            if (subtypeIndex >= 0) {
-                return imi.getSubtypeAt(subtypeIndex);
-            }
-        }
-
-        // If there are no selected subtypes, the framework will try to find the most applicable
-        // subtype from explicitly or implicitly enabled subtypes.
-        final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                getEnabledInputMethodSubtypeList(imi, true);
-        // If there is only one explicitly or implicitly enabled subtype, just returns it.
-        if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
-            return null;
-        }
-        if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
-            return explicitlyOrImplicitlyEnabledSubtypes.get(0);
-        }
-        final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString();
-        final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype(
-                explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
-                locale, true);
-        if (subtype != null) {
-            return subtype;
-        }
-        return SubtypeUtils.findLastResortApplicableSubtype(
-                explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
-    }
-
     @NonNull
     AdditionalSubtypeMap getNewAdditionalSubtypeMap(@NonNull String imeId,
             @NonNull ArrayList<InputMethodSubtype> subtypes,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 00e9d8d..47a79a3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1060,7 +1060,10 @@
         final boolean isInstallDpcPackagesPermissionGranted = (snapshot.checkUidPermission(
                 android.Manifest.permission.INSTALL_DPC_PACKAGES, mInstallerUid)
                 == PackageManager.PERMISSION_GRANTED);
-        final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId);
+        // Also query the package uid for archived packages, so that the user confirmation
+        // dialog can be displayed for updating archived apps.
+        final int targetPackageUid = snapshot.getPackageUid(packageName,
+                PackageManager.MATCH_ARCHIVED_PACKAGES, userId);
         final boolean isUpdate = targetPackageUid != -1 || isApexSession();
         final InstallSourceInfo existingInstallSourceInfo = isUpdate
                 ? snapshot.getInstallSourceInfo(packageName, userId)
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e0e8a03..e215ca3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5975,6 +5975,7 @@
     @VisibleForTesting
     void removeUserInfo(@UserIdInt int userId) {
         synchronized (mUsersLock) {
+            UserManager.invalidateUserSerialNumberCache();
             mUsers.remove(userId);
         }
     }
@@ -6400,6 +6401,7 @@
 
         // Remove this user from the list
         synchronized (mUsersLock) {
+            UserManager.invalidateUserSerialNumberCache();
             mUsers.remove(userId);
             mIsUserManaged.delete(userId);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d7592f..5f19924 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8179,8 +8179,7 @@
     }
 
     void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
-        if (mAppCompatController.getAppCompatOverrides()
-                .getAppCompatOrientationOverrides()
+        if (mAppCompatController.getAppCompatOrientationOverrides()
                 .shouldIgnoreRequestedOrientation(requestedOrientation)) {
             return;
         }
@@ -8559,7 +8558,7 @@
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
         final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM
-                && mLetterboxUiController.getFreeformCameraCompatMode()
+                && mAppCompatController.getAppCompatCameraOverrides().getFreeformCameraCompatMode()
                         != CAMERA_COMPAT_FREEFORM_NONE;
 
         // Bubble activities should always fill their parent and should not be letterboxed.
@@ -9887,7 +9886,8 @@
             return mLetterboxUiController.getUserMinAspectRatio();
         }
         if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
-                && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
+                && !mAppCompatController.getAppCompatCameraOverrides()
+                    .shouldOverrideMinAspectRatioForCamera()) {
             return info.getMinAspectRatio();
         }
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -10814,15 +10814,18 @@
         proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
         proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
         proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
-                mLetterboxUiController.shouldForceRotateForCameraCompat());
+                mAppCompatController.getAppCompatCameraOverrides()
+                        .shouldForceRotateForCameraCompat());
         proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
-                mLetterboxUiController.shouldRefreshActivityForCameraCompat());
+                mAppCompatController.getAppCompatCameraOverrides()
+                        .shouldRefreshActivityForCameraCompat());
         proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
-                mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
+                mAppCompatController.getAppCompatCameraOverrides()
+                        .shouldRefreshActivityViaPauseForCameraCompat());
         proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
                 mLetterboxUiController.shouldOverrideMinAspectRatio());
         proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
-                mAppCompatController.getAppCompatOverrides().getAppCompatOrientationOverrides()
+                mAppCompatController.getAppCompatOrientationOverrides()
                         .shouldIgnoreOrientationRequestLoop());
         proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
                 mLetterboxUiController.shouldOverrideForceResizeApp());
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 056c09e..0c32dfc 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -77,10 +77,10 @@
         final boolean cycleThroughStop =
                 mWmService.mLetterboxConfiguration
                         .isCameraCompatRefreshCycleThroughStopEnabled()
-                        && !activity.mLetterboxUiController
-                        .shouldRefreshActivityViaPauseForCameraCompat();
+                        && !activity.mAppCompatController.getAppCompatCameraOverrides()
+                            .shouldRefreshActivityViaPauseForCameraCompat();
 
-        activity.mLetterboxUiController.setIsRefreshRequested(true);
+        activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
         ProtoLog.v(WM_DEBUG_STATES,
                 "Refreshing activity for freeform camera compatibility treatment, "
                         + "activityRecord=%s", activity);
@@ -97,24 +97,26 @@
                 }
             }, REFRESH_CALLBACK_TIMEOUT_MS);
         } catch (RemoteException e) {
-            activity.mLetterboxUiController.setIsRefreshRequested(false);
+            activity.mAppCompatController.getAppCompatCameraOverrides()
+                    .setIsRefreshRequested(false);
         }
     }
 
     boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
-        return activity.mLetterboxUiController.isRefreshRequested();
+        return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
     }
 
     void onActivityRefreshed(@NonNull ActivityRecord activity) {
         // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
         //  state?
-        activity.mLetterboxUiController.setIsRefreshRequested(false);
+        activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
     }
 
     private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
             @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
         return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
-                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && activity.mAppCompatController.getAppCompatOverrides()
+                    .getAppCompatCameraOverrides().shouldRefreshActivityForCameraCompat()
                 && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
                 ((Evaluator) evaluator)
                         .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dded1ca..ded205e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6192,11 +6192,14 @@
             synchronized (mGlobalLockWithoutBoost) {
                 final WindowProcessController proc = mProcessNames.remove(name, uid);
                 if (proc != null && !mStartingProcessActivities.isEmpty()) {
-                    for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
-                        final ActivityRecord r = mStartingProcessActivities.get(i);
+                    // Use a copy in case finishIfPossible changes the list indirectly.
+                    final ArrayList<ActivityRecord> activities =
+                            new ArrayList<>(mStartingProcessActivities);
+                    for (int i = activities.size() - 1; i >= 0; i--) {
+                        final ActivityRecord r = activities.get(i);
                         if (uid == r.info.applicationInfo.uid && name.equals(r.processName)) {
                             Slog.w(TAG, proc + " is removed with pending start " + r);
-                            mStartingProcessActivities.remove(i);
+                            mStartingProcessActivities.remove(r);
                             // If visible, finish it to avoid getting stuck on screen.
                             if (r.isVisibleRequested()) {
                                 r.finishIfPossible("starting-proc-removed", false /* oomAdj */);
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
new file mode 100644
index 0000000..c0e5005
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
+
+import com.android.server.wm.utils.OptPropFactory;
+import com.android.window.flags.Flags;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Encapsulates app compat configurations and overrides related to camera.
+ */
+class AppCompatCameraOverrides {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME
+            ? "AppCompatCameraOverrides" : TAG_ATM;
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final AppCompatCameraOverridesState mAppCompatCameraOverridesState;
+    @NonNull
+    private final LetterboxConfiguration mLetterboxConfiguration;
+    @NonNull
+    private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
+    @NonNull
+    private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp;
+    @NonNull
+    private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
+    @NonNull
+    private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;
+
+    AppCompatCameraOverrides(@NonNull ActivityRecord activityRecord,
+            @NonNull LetterboxConfiguration letterboxConfiguration,
+            @NonNull OptPropFactory optPropBuilder) {
+        mActivityRecord = activityRecord;
+        mLetterboxConfiguration = letterboxConfiguration;
+        mAppCompatCameraOverridesState = new AppCompatCameraOverridesState();
+        mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
+                PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+        final BooleanSupplier isCameraCompatTreatmentEnabled = AppCompatUtils.asLazy(
+                mLetterboxConfiguration::isCameraCompatTreatmentEnabled);
+        mCameraCompatAllowRefreshOptProp = optPropBuilder.create(
+                PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH,
+                isCameraCompatTreatmentEnabled);
+        mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create(
+                PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
+                isCameraCompatTreatmentEnabled);
+        mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
+                PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
+                isCameraCompatTreatmentEnabled);
+    }
+
+    /**
+     * Whether we should apply the min aspect ratio per-app override only when an app is connected
+     * to the camera.
+     * When this override is applied the min aspect ratio given in the app's manifest will be
+     * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
+     * is higher. The treatment will also apply if no value is provided in the manifest.
+     *
+     * <p>This method returns {@code true} when the following conditions are met:
+     * <ul>
+     *     <li>Opt-out component property isn't enabled
+     *     <li>Per-app override is enabled
+     * </ul>
+     */
+    boolean shouldOverrideMinAspectRatioForCamera() {
+        return mActivityRecord.isCameraActive()
+                && mAllowMinAspectRatioOverrideOptProp
+                .shouldEnableWithOptInOverrideAndOptOutProperty(
+                        isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+    }
+
+    /**
+     * Whether activity is eligible for activity "refresh" after camera compat force rotation
+     * treatment. See {@link DisplayRotationCompatPolicy} for context.
+     *
+     * <p>This treatment is enabled when the following conditions are met:
+     * <ul>
+     *     <li>Flag gating the camera compat treatment is enabled.
+     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
+     *     developers with the component property.
+     * </ul>
+     */
+    boolean shouldRefreshActivityForCameraCompat() {
+        return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
+    }
+
+    /**
+     * Whether activity should be "refreshed" after the camera compat force rotation treatment
+     * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
+     * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
+     *
+     * <p>This treatment is enabled when the following conditions are met:
+     * <ul>
+     *     <li>Flag gating the camera compat treatment is enabled.
+     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
+     *     component property by the app developers.
+     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
+     *     manufacturer with override / by the app developers with the component property.
+     * </ul>
+     */
+    boolean shouldRefreshActivityViaPauseForCameraCompat() {
+        return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
+    }
+
+    /**
+     * Whether activity is eligible for camera compat force rotation treatment. See {@link
+     * DisplayRotationCompatPolicy} for context.
+     *
+     * <p>This treatment is enabled when the following conditions are met:
+     * <ul>
+     *     <li>Flag gating the camera compat treatment is enabled.
+     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
+     *     developers with the component property.
+     * </ul>
+     */
+    boolean shouldForceRotateForCameraCompat() {
+        return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
+    }
+
+    /**
+     * Whether activity is eligible for camera compatibility free-form treatment.
+     *
+     * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
+     * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
+     * provides changes to the camera and display orientation signals to match those expected on a
+     * portrait device in that orientation (for example, on a standard phone).
+     *
+     * <p>The treatment is enabled when the following conditions are met:
+     * <ul>
+     * <li>Property gating the camera compatibility free-form treatment is enabled.
+     * <li>Activity isn't opted out by the device manufacturer with override.
+     * </ul>
+     */
+    boolean shouldApplyFreeformTreatmentForCameraCompat() {
+        return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
+                OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
+    }
+
+    /**
+     * @return {@code true} if the configuration needs to be recomputed after a camera state update.
+     */
+    boolean shouldRecomputeConfigurationForCameraCompat() {
+        return isOverrideOrientationOnlyForCameraEnabled()
+                || isCameraCompatSplitScreenAspectRatioAllowed()
+                || shouldOverrideMinAspectRatioForCamera();
+    }
+
+    boolean isOverrideOrientationOnlyForCameraEnabled() {
+        return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
+    }
+
+    /**
+     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
+     */
+    boolean isRefreshRequested() {
+        return mAppCompatCameraOverridesState.mIsRefreshRequested;
+    }
+
+    /**
+     * @param isRequested Whether activity "refresh" was requested but not finished
+     *                    in {@link #activityResumedLocked}.
+     */
+    void setIsRefreshRequested(boolean isRequested) {
+        mAppCompatCameraOverridesState.mIsRefreshRequested = isRequested;
+    }
+
+    /**
+     * Whether we use split screen aspect ratio for the activity when camera compat treatment
+     * is active because the corresponding config is enabled and activity supports resizing.
+     */
+    boolean isCameraCompatSplitScreenAspectRatioAllowed() {
+        return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
+                && !mActivityRecord.shouldCreateCompatDisplayInsets();
+    }
+
+    @FreeformCameraCompatMode
+    int getFreeformCameraCompatMode() {
+        return mAppCompatCameraOverridesState.mFreeformCameraCompatMode;
+    }
+
+    void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
+        mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
+    }
+
+    private boolean isCompatChangeEnabled(long overrideChangeId) {
+        return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+    }
+
+    static class AppCompatCameraOverridesState {
+        // Whether activity "refresh" was requested but not finished in
+        // ActivityRecord#activityResumedLocked following the camera compat force rotation in
+        // DisplayRotationCompatPolicy.
+        private boolean mIsRefreshRequested;
+
+        @FreeformCameraCompatMode
+        private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
new file mode 100644
index 0000000..ee523a2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+
+/**
+ * Encapsulate the app compat logic related to camera.
+ */
+class AppCompatCameraPolicy {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME
+            ? "AppCompatCameraPolicy" : TAG_ATM;
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+
+    @NonNull
+    private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+
+    AppCompatCameraPolicy(@NonNull ActivityRecord activityRecord,
+            @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
+        mActivityRecord = activityRecord;
+        mAppCompatCameraOverrides = appCompatCameraOverrides;
+    }
+
+    void recomputeConfigurationForCameraCompatIfNeeded() {
+        if (mAppCompatCameraOverrides.shouldRecomputeConfigurationForCameraCompat()) {
+            mActivityRecord.recomputeConfiguration();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 4b0d739..d8c0c17 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -16,6 +16,9 @@
 package com.android.server.wm;
 
 import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+
+import com.android.server.wm.utils.OptPropFactory;
 
 /**
  * Allows the interaction with all the app compat policies and configurations
@@ -28,19 +31,26 @@
     private final AppCompatOrientationPolicy mOrientationPolicy;
     @NonNull
     private final AppCompatOverrides mAppCompatOverrides;
+    @NonNull
+    private final AppCompatCameraPolicy mAppCompatCameraPolicy;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
+        final PackageManager packageManager = wmService.mContext.getPackageManager();
+        final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
+                activityRecord.packageName);
         mTransparentPolicy = new TransparentPolicy(activityRecord,
                 wmService.mLetterboxConfiguration);
-        mAppCompatOverrides = new AppCompatOverrides(wmService, activityRecord,
-                wmService.mLetterboxConfiguration);
+        mAppCompatOverrides = new AppCompatOverrides(activityRecord,
+                wmService.mLetterboxConfiguration, optPropBuilder);
         // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with aspectRatio.
         final LetterboxUiController tmpController = activityRecord.mLetterboxUiController;
         mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord,
                 mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
                 tmpController::shouldApplyUserMinAspectRatioOverride,
                 tmpController::isSystemOverrideToFullscreenEnabled);
+        mAppCompatCameraPolicy = new AppCompatCameraPolicy(activityRecord,
+                mAppCompatOverrides.getAppCompatCameraOverrides());
     }
 
     @NonNull
@@ -54,7 +64,22 @@
     }
 
     @NonNull
+    AppCompatCameraPolicy getAppCompatCameraPolicy() {
+        return mAppCompatCameraPolicy;
+    }
+
+    @NonNull
     AppCompatOverrides getAppCompatOverrides() {
         return mAppCompatOverrides;
     }
+
+    @NonNull
+    AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
+        return mAppCompatOverrides.getAppCompatOrientationOverrides();
+    }
+
+    @NonNull
+    AppCompatCameraOverrides getAppCompatCameraOverrides() {
+        return mAppCompatOverrides.getAppCompatCameraOverrides();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index 446a75c..b0fdbb5 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -28,7 +28,7 @@
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.AppCompatOverrides.asLazy;
+import static com.android.server.wm.AppCompatUtils.asLazy;
 
 import android.annotation.NonNull;
 import android.content.pm.ActivityInfo;
@@ -60,9 +60,9 @@
     @NonNull
     final OrientationOverridesState mOrientationOverridesState;
 
-    AppCompatOrientationOverrides(@NonNull OptPropFactory optPropBuilder,
-                                   @NonNull LetterboxConfiguration letterboxConfiguration,
-                                   @NonNull ActivityRecord activityRecord) {
+    AppCompatOrientationOverrides(@NonNull ActivityRecord activityRecord,
+            @NonNull LetterboxConfiguration letterboxConfiguration,
+            @NonNull OptPropFactory optPropBuilder) {
         mActivityRecord = activityRecord;
         mOrientationOverridesState = new OrientationOverridesState(mActivityRecord,
                 System::currentTimeMillis);
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 8e9a9e9..960ef5a 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -103,11 +103,11 @@
             return candidate;
         }
 
-        if (displayContent != null && mAppCompatOverrides
+        if (displayContent != null && mAppCompatOverrides.getAppCompatCameraOverrides()
                 .isOverrideOrientationOnlyForCameraEnabled()
-                && (displayContent.mDisplayRotationCompatPolicy == null
-                || !displayContent.mDisplayRotationCompatPolicy
-                .isActivityEligibleForOrientationOverride(mActivityRecord))) {
+                    && (displayContent.mDisplayRotationCompatPolicy == null
+                    || !displayContent.mDisplayRotationCompatPolicy
+                        .isActivityEligibleForOrientationOverride(mActivityRecord))) {
             return candidate;
         }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 794008a..c20da7c 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -19,22 +19,13 @@
 import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
-import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
@@ -47,12 +38,8 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.NonNull;
-import android.content.pm.PackageManager;
 
 import com.android.server.wm.utils.OptPropFactory;
-import com.android.window.flags.Flags;
-
-import java.util.function.BooleanSupplier;
 
 /**
  * Encapsulate logic related to operations guarded by an app override.
@@ -70,12 +57,6 @@
     @NonNull
     private final OptPropFactory.OptProp mFakeFocusOptProp;
     @NonNull
-    private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;
-    @NonNull
-    private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp;
-    @NonNull
-    private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
-    @NonNull
     private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
     @NonNull
     private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
@@ -87,37 +68,25 @@
     private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp;
     @NonNull
     private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;
-
+    @NonNull
     private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
+    @NonNull
+    private final AppCompatCameraOverrides mAppCompatCameraOverrides;
 
-    AppCompatOverrides(@NonNull WindowManagerService wmService,
-                        @NonNull ActivityRecord activityRecord,
-                        @NonNull LetterboxConfiguration letterboxConfiguration) {
+    AppCompatOverrides(@NonNull ActivityRecord activityRecord,
+            @NonNull LetterboxConfiguration letterboxConfiguration,
+            @NonNull OptPropFactory optPropBuilder) {
         mLetterboxConfiguration = letterboxConfiguration;
         mActivityRecord = activityRecord;
-        final PackageManager packageManager = wmService.mContext.getPackageManager();
 
-        final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
-                activityRecord.packageName);
-
-        mAppCompatOrientationOverrides =
-                new AppCompatOrientationOverrides(optPropBuilder, mLetterboxConfiguration,
-                        mActivityRecord);
+        mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
+                mLetterboxConfiguration, optPropBuilder);
+        mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord,
+                mLetterboxConfiguration, optPropBuilder);
 
         mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
                 mLetterboxConfiguration::isCompatFakeFocusEnabled);
 
-        final BooleanSupplier isCameraCompatTreatmentEnabled = asLazy(
-                mLetterboxConfiguration::isCameraCompatTreatmentEnabled);
-        mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
-                PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
-                isCameraCompatTreatmentEnabled);
-        mCameraCompatAllowRefreshOptProp = optPropBuilder.create(
-                PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH,
-                isCameraCompatTreatmentEnabled);
-        mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create(
-                PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
-                isCameraCompatTreatmentEnabled);
 
         mAllowOrientationOverrideOptProp = optPropBuilder.create(
                 PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
@@ -162,6 +131,11 @@
         return mAppCompatOrientationOverrides;
     }
 
+    @NonNull
+    AppCompatCameraOverrides getAppCompatCameraOverrides() {
+        return mAppCompatCameraOverrides;
+    }
+
     /**
      * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
      * because some game engines wait to get focus before drawing the content of the app which isn't
@@ -179,57 +153,6 @@
                 isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS));
     }
 
-    /**
-     * Whether activity is eligible for camera compat force rotation treatment. See {@link
-     * DisplayRotationCompatPolicy} for context.
-     *
-     * <p>This treatment is enabled when the following conditions are met:
-     * <ul>
-     *     <li>Flag gating the camera compat treatment is enabled.
-     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
-     *     developers with the component property.
-     * </ul>
-     */
-    boolean shouldForceRotateForCameraCompat() {
-        return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
-                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
-    }
-
-    /**
-     * Whether activity is eligible for activity "refresh" after camera compat force rotation
-     * treatment. See {@link DisplayRotationCompatPolicy} for context.
-     *
-     * <p>This treatment is enabled when the following conditions are met:
-     * <ul>
-     *     <li>Flag gating the camera compat treatment is enabled.
-     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
-     *     developers with the component property.
-     * </ul>
-     */
-    boolean shouldRefreshActivityForCameraCompat() {
-        return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
-                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
-    }
-
-    /**
-     * Whether activity should be "refreshed" after the camera compat force rotation treatment
-     * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
-     * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
-     *
-     * <p>This treatment is enabled when the following conditions are met:
-     * <ul>
-     *     <li>Flag gating the camera compat treatment is enabled.
-     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
-     *     component property by the app developers.
-     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
-     *     manufacturer with override / by the app developers with the component property.
-     * </ul>
-     */
-    boolean shouldRefreshActivityViaPauseForCameraCompat() {
-        return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
-                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
-    }
-
     boolean isSystemOverrideToFullscreenEnabled(int userAspectRatio) {
         return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
                 && !mAllowOrientationOverrideOptProp.isFalse()
@@ -262,50 +185,6 @@
         return isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
     }
 
-    boolean isOverrideOrientationOnlyForCameraEnabled() {
-        return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
-    }
-
-    /**
-     * Whether activity is eligible for camera compatibility free-form treatment.
-     *
-     * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
-     * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
-     * provides changes to the camera and display orientation signals to match those expected on a
-     * portrait device in that orientation (for example, on a standard phone).
-     *
-     * <p>The treatment is enabled when the following conditions are met:
-     * <ul>
-     * <li>Property gating the camera compatibility free-form treatment is enabled.
-     * <li>Activity isn't opted out by the device manufacturer with override.
-     * </ul>
-     */
-    boolean shouldApplyFreeformTreatmentForCameraCompat() {
-        return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
-                OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
-    }
-
-
-    /**
-     * Whether we should apply the min aspect ratio per-app override only when an app is connected
-     * to the camera.
-     * When this override is applied the min aspect ratio given in the app's manifest will be
-     * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
-     * is higher. The treatment will also apply if no value is provided in the manifest.
-     *
-     * <p>This method returns {@code true} when the following conditions are met:
-     * <ul>
-     *     <li>Opt-out component property isn't enabled
-     *     <li>Per-app override is enabled
-     * </ul>
-     */
-    boolean shouldOverrideMinAspectRatioForCamera() {
-        return mActivityRecord.isCameraActive()
-                && mAllowMinAspectRatioOverrideOptProp
-                    .shouldEnableWithOptInOverrideAndOptOutProperty(
-                        isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
-    }
-
     /**
      * Whether should fix display orientation to landscape natural orientation when a task is
      * fullscreen and the display is ignoring orientation requests.
@@ -382,21 +261,4 @@
     private boolean isCompatChangeEnabled(long overrideChangeId) {
         return mActivityRecord.info.isChangeEnabled(overrideChangeId);
     }
-
-    @NonNull
-    static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) {
-        return new BooleanSupplier() {
-            private boolean mRead;
-            private boolean mValue;
-
-            @Override
-            public boolean getAsBoolean() {
-                if (!mRead) {
-                    mRead = true;
-                    mValue = supplier.getAsBoolean();
-                }
-                return mValue;
-            }
-        };
-    }
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
new file mode 100644
index 0000000..be51dd3b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utilities for App Compat policies and overrides.
+ */
+class AppCompatUtils {
+
+    /**
+     * Lazy version of a {@link BooleanSupplier} which access an existing BooleanSupplier and
+     * caches the value.
+     *
+     * @param supplier The BooleanSupplier to decorate.
+     * @return A lazy implementation of a BooleanSupplier
+     */
+    @NonNull
+    static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) {
+        return new BooleanSupplier() {
+            private boolean mRead;
+            private boolean mValue;
+
+            @Override
+            public boolean getAsBoolean() {
+                if (!mRead) {
+                    mRead = true;
+                    mValue = supplier.getAsBoolean();
+                }
+                return mValue;
+            }
+        };
+    }
+}
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 9204017..68a4172 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -110,12 +110,14 @@
         if (!isTreatmentEnabledForActivity(cameraActivity)) {
             return false;
         }
-        final int existingCameraCompatMode =
-                cameraActivity.mLetterboxUiController.getFreeformCameraCompatMode();
+        final int existingCameraCompatMode = cameraActivity.mAppCompatController
+                .getAppCompatCameraOverrides()
+                        .getFreeformCameraCompatMode();
         final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
         if (newCameraCompatMode != existingCameraCompatMode) {
             mIsCameraCompatTreatmentPending = true;
-            cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(newCameraCompatMode);
+            cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
+                    .setFreeformCameraCompatMode(newCameraCompatMode);
             forceUpdateActivityAndTask(cameraActivity);
             return true;
         } else {
@@ -134,8 +136,8 @@
                     mDisplayContent.mDisplayId, cameraId);
             return false;
         }
-        cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(
-                CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
+        cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
         forceUpdateActivityAndTask(cameraActivity);
         mIsCameraCompatTreatmentPending = false;
         return true;
@@ -191,6 +193,6 @@
                 || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
             return false;
         }
-        return topActivity.mLetterboxUiController.isRefreshRequested();
+        return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index beb3767..3d71e95 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -228,9 +228,11 @@
                 != lastReportedConfig.windowConfiguration.getDisplayRotation());
         return isTreatmentEnabledForDisplay()
                 && isTreatmentEnabledForActivity(activity)
-                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                    .shouldRefreshActivityForCameraCompat()
                 && (displayRotationChanged
-                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
+                || activity.mAppCompatController.getAppCompatCameraOverrides()
+                        .isCameraCompatSplitScreenAspectRatioAllowed());
     }
 
     /**
@@ -254,7 +256,8 @@
     boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
         return isTreatmentEnabledForDisplay()
                 && isCameraActive(activity, /* mustBeFullscreen */ true)
-                && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
+                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                    .shouldForceRotateForCameraCompat();
     }
 
     /**
@@ -286,7 +289,8 @@
                 // handle dynamic changes so we shouldn't force rotate them.
                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
-                && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
+                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                    .shouldForceRotateForCameraCompat();
     }
 
     @Override
@@ -295,7 +299,8 @@
         // Checking whether an activity in fullscreen rather than the task as this camera
         // compat treatment doesn't cover activity embedding.
         if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-            cameraActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
+            cameraActivity.mAppCompatController
+                    .getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
             mDisplayContent.updateOrientation();
             return true;
         }
@@ -362,7 +367,8 @@
                 || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
             return true;
         }
-        topActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
+        topActivity.mAppCompatController
+                .getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
         mDisplayContent.updateOrientation();
         return true;
     }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index f70d2a5..872b4e1 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
@@ -657,7 +656,6 @@
             final boolean lastKeyguardGoingAway = mKeyguardGoingAway;
 
             final ActivityRecord lastDismissKeyguardActivity = mDismissingKeyguardActivity;
-            final ActivityRecord lastTurnScreenOnActivity = mTopTurnScreenOnActivity;
 
             mRequestDismissKeyguard = false;
             mOccluded = false;
@@ -666,7 +664,6 @@
             mDismissingKeyguardActivity = null;
             mTopTurnScreenOnActivity = null;
 
-            boolean occludedByActivity = false;
             final Task task = getRootTaskForControllingOccluding(display);
             final ActivityRecord top = task != null ? task.getTopNonFinishingActivity() : null;
             if (top != null) {
@@ -712,7 +709,7 @@
 
             if (mTopTurnScreenOnActivity != null
                     && !mService.mWindowManager.mPowerManager.isInteractive()
-                    && (mRequestDismissKeyguard || occludedByActivity)) {
+                    && (mRequestDismissKeyguard || mOccluded)) {
                 controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
                 mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
             }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 85eeab1..e924fb6 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
@@ -65,7 +64,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.TaskDescription;
-import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -113,18 +111,10 @@
     @Nullable
     private Letterbox mLetterbox;
 
-    // Whether activity "refresh" was requested but not finished in
-    // ActivityRecord#activityResumedLocked following the camera compat force rotation in
-    // DisplayRotationCompatPolicy.
-    private boolean mIsRefreshRequested;
-
     private boolean mLastShouldShowLetterboxUi;
 
     private boolean mDoubleTapEvent;
 
-    @FreeformCameraCompatMode
-    private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
-
     LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
         mLetterboxConfiguration = wmService.mLetterboxConfiguration;
         // Given activityRecord may not be fully constructed since LetterboxUiController
@@ -147,13 +137,6 @@
         }
     }
 
-
-    @VisibleForTesting
-    int getSetOrientationRequestCounter() {
-        return getAppCompatOverrides().getAppCompatOrientationOverrides()
-                .getSetOrientationRequestCounter();
-    }
-
     /**
      * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
      * because some game engines wait to get focus before drawing the content of the app which isn't
@@ -187,23 +170,6 @@
     }
 
     /**
-     * Whether we should apply the min aspect ratio per-app override only when an app is connected
-     * to the camera.
-     * When this override is applied the min aspect ratio given in the app's manifest will be
-     * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
-     * is higher. The treatment will also apply if no value is provided in the manifest.
-     *
-     * <p>This method returns {@code true} when the following conditions are met:
-     * <ul>
-     *     <li>Opt-out component property isn't enabled
-     *     <li>Per-app override is enabled
-     * </ul>
-     */
-    boolean shouldOverrideMinAspectRatioForCamera() {
-        return getAppCompatOverrides().shouldOverrideMinAspectRatioForCamera();
-    }
-
-    /**
      * Whether we should apply the force resize per-app override. When this override is applied it
      * forces the packages it is applied to to be resizable. It won't change whether the app can be
      * put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -242,16 +208,6 @@
                 .setRelaunchingAfterRequestedOrientationChanged(isRelaunching);
     }
 
-    /**
-     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
-     */
-    boolean isRefreshRequested() {
-        return mIsRefreshRequested;
-    }
-
-    void setIsRefreshRequested(boolean isRequested) {
-        mIsRefreshRequested = isRequested;
-    }
 
     boolean isOverrideRespectRequestedOrientationEnabled() {
         return getAppCompatOverrides().isOverrideRespectRequestedOrientationEnabled();
@@ -274,85 +230,6 @@
         return getAppCompatOverrides().shouldUseDisplayLandscapeNaturalOrientation();
     }
 
-    boolean isOverrideOrientationOnlyForCameraEnabled() {
-        return getAppCompatOverrides().isOverrideOrientationOnlyForCameraEnabled();
-    }
-
-    /**
-     * Whether activity is eligible for activity "refresh" after camera compat force rotation
-     * treatment. See {@link DisplayRotationCompatPolicy} for context.
-     *
-     * <p>This treatment is enabled when the following conditions are met:
-     * <ul>
-     *     <li>Flag gating the camera compat treatment is enabled.
-     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
-     *     developers with the component property.
-     * </ul>
-     */
-    boolean shouldRefreshActivityForCameraCompat() {
-        return getAppCompatOverrides().shouldRefreshActivityForCameraCompat();
-    }
-
-    /**
-     * Whether activity should be "refreshed" after the camera compat force rotation treatment
-     * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
-     * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
-     *
-     * <p>This treatment is enabled when the following conditions are met:
-     * <ul>
-     *     <li>Flag gating the camera compat treatment is enabled.
-     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
-     *     component property by the app developers.
-     *     <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
-     *     manufacturer with override / by the app developers with the component property.
-     * </ul>
-     */
-    boolean shouldRefreshActivityViaPauseForCameraCompat() {
-        return getAppCompatOverrides().shouldRefreshActivityViaPauseForCameraCompat();
-    }
-
-    /**
-     * Whether activity is eligible for camera compat force rotation treatment. See {@link
-     * DisplayRotationCompatPolicy} for context.
-     *
-     * <p>This treatment is enabled when the following conditions are met:
-     * <ul>
-     *     <li>Flag gating the camera compat treatment is enabled.
-     *     <li>Activity isn't opted out by the device manufacturer with override or by the app
-     *     developers with the component property.
-     * </ul>
-     */
-    boolean shouldForceRotateForCameraCompat() {
-        return getAppCompatOverrides().shouldForceRotateForCameraCompat();
-    }
-
-    /**
-     * Whether activity is eligible for camera compatibility free-form treatment.
-     *
-     * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
-     * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
-     * provides changes to the camera and display orientation signals to match those expected on a
-     * portrait device in that orientation (for example, on a standard phone).
-     *
-     * <p>The treatment is enabled when the following conditions are met:
-     * <ul>
-     * <li>Property gating the camera compatibility free-form treatment is enabled.
-     * <li>Activity isn't opted out by the device manufacturer with override.
-     * </ul>
-     */
-    boolean shouldApplyFreeformTreatmentForCameraCompat() {
-        return getAppCompatOverrides().shouldApplyFreeformTreatmentForCameraCompat();
-    }
-
-    @FreeformCameraCompatMode
-    int getFreeformCameraCompatMode() {
-        return mFreeformCameraCompatMode;
-    }
-
-    void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
-        mFreeformCameraCompatMode = freeformCameraCompatMode;
-    }
-
     private boolean isCompatChangeEnabled(long overrideChangeId) {
         return mActivityRecord.info.isChangeEnabled(overrideChangeId);
     }
@@ -574,27 +451,10 @@
                         : getDefaultMinAspectRatio();
     }
 
-    void recomputeConfigurationForCameraCompatIfNeeded() {
-        if (isOverrideOrientationOnlyForCameraEnabled()
-                || isCameraCompatSplitScreenAspectRatioAllowed()
-                || shouldOverrideMinAspectRatioForCamera()) {
-            mActivityRecord.recomputeConfiguration();
-        }
-    }
-
     boolean isLetterboxEducationEnabled() {
         return mLetterboxConfiguration.getIsEducationEnabled();
     }
 
-    /**
-     * Whether we use split screen aspect ratio for the activity when camera compat treatment
-     * is active because the corresponding config is enabled and activity supports resizing.
-     */
-    boolean isCameraCompatSplitScreenAspectRatioAllowed() {
-        return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
-                && !mActivityRecord.shouldCreateCompatDisplayInsets();
-    }
-
     private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
         final boolean isBookMode = isDisplayFullScreenAndInPosture(/* isTabletop */ false);
         final boolean isNotCenteredHorizontally = getHorizontalPositionMultiplier(
@@ -605,8 +465,9 @@
 
         // Don't resize to split screen size when in book mode if letterbox position is centered
         return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
-                    || isCameraCompatSplitScreenAspectRatioAllowed()
-                        && getAppCompatOverrides().isCameraCompatTreatmentActive();
+                    || mActivityRecord.mAppCompatController.getAppCompatCameraOverrides()
+                            .isCameraCompatSplitScreenAspectRatioAllowed()
+                                && getAppCompatOverrides().isCameraCompatTreatmentActive();
     }
 
     private float getDefaultMinAspectRatioForUnresizableApps() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 52a1ed9..f0b0e91 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3522,7 +3522,8 @@
         appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top == null
                 ? CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE
-                : top.mLetterboxUiController.getFreeformCameraCompatMode();
+                : top.mAppCompatController.getAppCompatCameraOverrides()
+                        .getFreeformCameraCompatMode();
     }
 
     /**
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 33ea9b4..9e46f2f 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import static java.util.Objects.requireNonNull;
 
@@ -134,8 +135,10 @@
 
     @Test
     public void testApplyImeVisibility_hideImeExplicit() throws Exception {
-        mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
         synchronized (ImfLock.class) {
+            final var bindingController =
+                    mInputMethodManagerService.getInputMethodBindingController(mUserId);
+            when(bindingController.getImeWindowVis()).thenReturn(IME_ACTIVE);
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
@@ -144,8 +147,10 @@
 
     @Test
     public void testApplyImeVisibility_hideNotAlways() throws Exception {
-        mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
         synchronized (ImfLock.class) {
+            final var bindingController =
+                    mInputMethodManagerService.getInputMethodBindingController(mUserId);
+            when(bindingController.getImeWindowVis()).thenReturn(IME_ACTIVE);
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
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 7b8b712..d070aaa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1700,10 +1700,12 @@
         verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
     }
 
-    @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON)
     @Test
     public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOnNotCalledIfUnblocked() {
         // Set up.
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        when(mDisplayManagerFlagsMock.isOffloadSessionCancelBlockScreenOnEnabled())
+                .thenReturn(true);
         int initState = Display.STATE_OFF;
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1737,10 +1739,12 @@
         verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
     }
 
-    @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON)
     @Test
     public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOn() {
         // Set up.
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        when(mDisplayManagerFlagsMock.isOffloadSessionCancelBlockScreenOnEnabled())
+                .thenReturn(true);
         int initState = Display.STATE_OFF;
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
index 6092ef6..449521b 100644
--- a/services/tests/dreamservicetests/AndroidManifest.xml
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -53,6 +53,35 @@
                 android:name="android.service.dream"
                 android:resource="@xml/test_dream_metadata_invalid" />
         </service>
+
+        <service
+            android:name="com.android.server.dreams.TestDreamServiceWithNonexistentSettings"
+            android:exported="false"
+            android:label="Test Dream" >
+            <intent-filter>
+                <action android:name="android.service.dreams.DreamService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data
+                android:name="android.service.dream"
+                android:resource="@xml/test_dream_metadata_nonexistent_settings" />
+        </service>
+
+        <service
+            android:name="com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings"
+            android:exported="false"
+            android:label="Test Dream" >
+            <intent-filter>
+                <action android:name="android.service.dreams.DreamService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data
+                android:name="android.service.dream"
+                android:resource="@xml/test_dream_metadata_nopackage_nonexistent_settings" />
+        </service>
+
+        <activity android:name=".TestDreamSettingsActivity">
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
new file mode 100644
index 0000000..f91f058
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<!-- The settings activity does not exist, which is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="com.android.frameworks.dreamservicetests/.TestDreamSettingsActivityNonexistent"
+    android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
new file mode 100644
index 0000000..24032c9
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<!-- The settings activity's ComponentName is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="com.android.frameworks.dreamservicetests.TestDreamSettingsActivityNonexistentNoPackage"
+    android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index b98af6b..b4e1abf 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -91,6 +91,28 @@
     }
 
     @Test
+    public void testMetadataParsing_nonexistentSettingsActivity()
+            throws PackageManager.NameNotFoundException {
+        final String testDreamClassName =
+                "com.android.server.dreams.TestDreamServiceWithNonexistentSettings";
+        final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+        assertThat(metadata.settingsActivity).isNull();
+        assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+    }
+
+    @Test
+    public void testMetadataParsing_noPackage_nonexistentSettingsActivity()
+            throws PackageManager.NameNotFoundException {
+        final String testDreamClassName =
+                "com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings";
+        final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+        assertThat(metadata.settingsActivity).isNull();
+        assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+    }
+
+    @Test
     public void testMetadataParsing_exceptionReading() {
         final PackageManager packageManager = Mockito.mock(PackageManager.class);
         final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
new file mode 100644
index 0000000..bb8db8a
--- /dev/null
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+public class TestDreamSettingsActivity {
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 123f0ed..ca30551 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -28,6 +28,7 @@
 import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
 import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
 
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
@@ -163,6 +164,7 @@
         mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
         mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), HARDWARE);
         mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), SOFTWARE);
+        mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), GESTURE);
         mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
         mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
@@ -186,6 +188,7 @@
         assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
         assertTrue(mUserState.getShortcutTargetsLocked(HARDWARE).isEmpty());
         assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
+        assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
         assertNull(mUserState.getTargetAssignedToAccessibilityButton());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
         assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 2a4b797..21364b8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2012,38 +2012,94 @@
     }
 
     @Test
-    public void handleStandby_fromActiveSource_standby() {
-        mPowerManager.setInteractive(true);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+    public void handleStandby_fromActiveSource_previousActiveSourceSet_standby() {
+        mHdmiCecLocalDeviceTv = new MockTvDevice(mHdmiControlService);
+        HdmiCecMessage activeSourceFromPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x1000);
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
         mTestLooper.dispatchAll();
 
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isFalse();
+        mPowerManager.setInteractive(true);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.dispatchMessage(activeSourceFromPlayback);
         mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
                 "HdmiCecLocalDeviceTvTest");
         mTestLooper.dispatchAll();
 
-        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
-                ADDR_TV);
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isTrue();
         assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
                 .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         assertThat(mPowerManager.isInteractive()).isFalse();
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isFalse();
     }
 
     @Test
-    public void handleStandby_fromNonActiveSource_noStandby() {
+    public void handleStandby_fromNonActiveSource_previousActiveSourceSet_noStandby() {
+        HdmiCecMessage activeSourceFromPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_2, 0x2000);
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
+        mHdmiCecLocalDeviceTv = new MockTvDevice(mHdmiControlService);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isFalse();
         mPowerManager.setInteractive(true);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+        mHdmiCecLocalDeviceTv.dispatchMessage(activeSourceFromPlayback);
         mHdmiControlService.setActiveSource(ADDR_PLAYBACK_2, 0x2000,
                 "HdmiCecLocalDeviceTvTest");
         mTestLooper.dispatchAll();
 
-        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
-                ADDR_TV);
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isTrue();
         assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
                 .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         assertThat(mPowerManager.isInteractive()).isTrue();
     }
+
+
+    @Test
+    public void handleStandby_fromNonActiveSource_previousActiveSourceNotSet_Standby() {
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
+        mHdmiCecLocalDeviceTv = new MockTvDevice(mHdmiControlService);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isFalse();
+        mPowerManager.setInteractive(true);
+
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isFalse();
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mPowerManager.isInteractive()).isFalse();
+        assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+                .isFalse();
+    }
+
+    protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
+        MockTvDevice(HdmiControlService service) {
+            super(service);
+        }
+
+        @Override
+        protected int handleActiveSource(HdmiCecMessage message) {
+            setWasActiveSourceSetToConnectedDevice(true);
+            return super.handleActiveSource(message);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 37065fd..02d3b59 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1424,6 +1424,39 @@
 
     @MediumTest
     @Test
+    public void testSerialNumberAfterUserRemoval() {
+        final UserInfo user = mUserManager.createUser("Test User", 0);
+        assertThat(user).isNotNull();
+
+        final int userId = user.id;
+        assertThat(mUserManager.getUserSerialNumber(userId))
+            .isEqualTo(user.serialNumber);
+        mUsersToRemove.add(userId);
+        removeUser(userId);
+        int serialNumber = mUserManager.getUserSerialNumber(userId);
+        int timeout = REMOVE_USER_TIMEOUT_SECONDS * 5; // called every 200ms
+
+        // Wait for the user to be removed from memory
+        while(serialNumber > 0 && timeout > 0){
+          sleep(200);
+          timeout--;
+          serialNumber = mUserManager.getUserSerialNumber(userId);
+        }
+        assertThat(serialNumber).isEqualTo(-1);
+    }
+
+
+    private void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @MediumTest
+    @Test
     public void testMaxUsers() {
         int N = UserManager.getMaxSupportedUsers();
         int count = mUserManager.getUsers().size();
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 12ab3e1..a3252f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -103,8 +103,8 @@
     @Test
     public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
         configureActivityAndDisplay();
-        when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
-                .thenReturn(false);
+        when(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldRefreshActivityForCameraCompat()).thenReturn(false);
         mActivityRefresher.addEvaluator(mEvaluatorTrue);
 
         mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -160,8 +160,9 @@
             throws Exception {
         configureActivityAndDisplay();
         mActivityRefresher.addEvaluator(mEvaluatorTrue);
-        doReturn(true).when(mActivity.mLetterboxUiController)
-                .shouldRefreshActivityViaPauseForCameraCompat();
+        doReturn(true)
+                .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+                    .shouldRefreshActivityViaPauseForCameraCompat();
 
         mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
 
@@ -172,8 +173,9 @@
     public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
         configureActivityAndDisplay();
         mActivityRefresher.addEvaluator(mEvaluatorTrue);
-        doReturn(true).when(mActivity.mLetterboxUiController)
-                .shouldRefreshActivityViaPauseForCameraCompat();
+        doReturn(true)
+                .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+                    .shouldRefreshActivityViaPauseForCameraCompat();
 
         mActivityRefresher.onActivityRefreshed(mActivity);
 
@@ -186,8 +188,8 @@
 
     private void assertActivityRefreshRequested(boolean refreshRequested,
             boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
-                .setIsRefreshRequested(true);
+        verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+                times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
                 cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -211,9 +213,9 @@
                 .getTopMostActivity();
 
         spyOn(mActivity.mLetterboxUiController);
-        doReturn(true).when(
-                mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
-
+        spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
         doReturn(true).when(mActivity).inFreeformWindowingMode();
+        doReturn(true).when(mActivity.mAppCompatController
+                .getAppCompatCameraOverrides()).shouldRefreshActivityForCameraCompat();
     }
 }
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 5f2853a..467050e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -149,6 +149,10 @@
                 .mLetterboxUiController).shouldApplyUserMinAspectRatioOverride();
     }
 
+    void setShouldCreateCompatDisplayInsets(boolean enabled) {
+        doReturn(enabled).when(mActivityStack.top()).shouldCreateCompatDisplayInsets();
+    }
+
     void setShouldApplyUserFullscreenOverride(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top()
                 .mLetterboxUiController).shouldApplyUserFullscreenOverride();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
new file mode 100644
index 0000000..9263b4f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.testng.Assert;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatCameraOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatCameraOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatCameraOverridesTest extends WindowTestsBase {
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Test
+    public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(false);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+    public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+    public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityForCameraCompat(false);
+        });
+    }
+
+    @Test
+    public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityForCameraCompat(false);
+        });
+    }
+
+    @Test
+    public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityForCameraCompat(true);
+        });
+    }
+
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+    public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(false);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityViaPauseForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+    public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityViaPauseForCameraCompat(true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+    public void testShouldRefreshActivityViaPauseForCameraCompat_propertyFalseAndOverrideFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().disable(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityViaPauseForCameraCompat(false);
+        });
+    }
+
+    @Test
+    public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().enable(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldRefreshActivityViaPauseForCameraCompat(true);
+        });
+    }
+
+    @Test
+    public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(false);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldForceRotateForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+    public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldForceRotateForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+    public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldForceRotateForCameraCompat(false);
+        });
+    }
+
+    @Test
+    public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldForceRotateForCameraCompat(false);
+        });
+    }
+
+    @Test
+    public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatTreatment(true);
+            robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldForceRotateForCameraCompat(true);
+        });
+    }
+
+    @Test
+    @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+    @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+    @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
+            OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void testShouldRecomputeConfigurationForCameraCompat() {
+        runTestScenario((robot) -> {
+            robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
+            robot.activity().createActivityWithComponentInNewTask();
+            robot.activateCamera(true);
+            robot.activity().setShouldCreateCompatDisplayInsets(false);
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+        });
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<CameraOverridesRobotTest> consumer) {
+        spyOn(mWm.mLetterboxConfiguration);
+        final CameraOverridesRobotTest robot = new CameraOverridesRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class CameraOverridesRobotTest extends AppCompatRobotBase {
+
+        CameraOverridesRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        void activateCamera(boolean isCameraActive) {
+            doReturn(isCameraActive).when(activity().top()).isCameraActive();
+        }
+
+        void checkShouldRefreshActivityForCameraCompat(boolean expected) {
+            Assert.assertEquals(getAppCompatCameraOverrides()
+                    .shouldRefreshActivityForCameraCompat(), expected);
+        }
+
+        void checkShouldRefreshActivityViaPauseForCameraCompat(boolean expected) {
+            Assert.assertEquals(getAppCompatCameraOverrides()
+                    .shouldRefreshActivityViaPauseForCameraCompat(), expected);
+        }
+
+        void checkShouldForceRotateForCameraCompat(boolean expected) {
+            Assert.assertEquals(getAppCompatCameraOverrides()
+                    .shouldForceRotateForCameraCompat(), expected);
+        }
+
+        void checkShouldApplyFreeformTreatmentForCameraCompat(boolean expected) {
+            Assert.assertEquals(getAppCompatCameraOverrides()
+                    .shouldApplyFreeformTreatmentForCameraCompat(), expected);
+        }
+
+        private AppCompatCameraOverrides getAppCompatCameraOverrides() {
+            return activity().top().mAppCompatController.getAppCompatCameraOverrides();
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
new file mode 100644
index 0000000..4116313
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatCameraPolicy}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatCameraPolicyTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatCameraPolicyTest extends WindowTestsBase {
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+    public void testRecomputeConfigurationForCameraCompatIfNeeded_allDisabledNoRecompute() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
+            robot.activateCamera(/* isCameraActive */ false);
+
+            robot.recomputeConfigurationForCameraCompatIfNeeded();
+            robot.checkRecomputeConfigurationInvoked(/* invoked */ false);
+
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+    public void testRecomputeConfigurationForCameraCompatIfNeeded_cameraEnabledRecompute() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
+            robot.activateCamera(/* isCameraActive */ false);
+
+            robot.recomputeConfigurationForCameraCompatIfNeeded();
+            robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+    public void testRecomputeConfigurationForCameraSplitScreenCompatIfNeeded_recompute() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
+            robot.activateCamera(/* isCameraActive */ false);
+
+            robot.recomputeConfigurationForCameraCompatIfNeeded();
+            robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+    @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+    public void testRecomputeConfigurationForCameraSplitScreenCompatIfNeededWithCamera_recompute() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
+            robot.activateCamera(/* isCameraActive */ true);
+
+            robot.recomputeConfigurationForCameraCompatIfNeeded();
+            robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+        });
+    }
+
+    void runTestScenario(@NonNull Consumer<CameraPolicyRobotTest> consumer) {
+        spyOn(mWm.mLetterboxConfiguration);
+        final CameraPolicyRobotTest robot = new CameraPolicyRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class CameraPolicyRobotTest extends AppCompatRobotBase {
+
+        private final WindowManagerService mWm;
+
+        CameraPolicyRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+            mWm = wm;
+            spyOn(mWm);
+        }
+
+        void activateCamera(boolean isCameraActive) {
+            doReturn(isCameraActive).when(activity().top()).isCameraActive();
+        }
+
+        void recomputeConfigurationForCameraCompatIfNeeded() {
+            getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
+        }
+
+        void checkRecomputeConfigurationInvoked(boolean invoked) {
+            if (invoked) {
+                verify(activity().top()).recomputeConfiguration();
+            } else {
+                verify(activity().top(), never()).recomputeConfiguration();
+            }
+        }
+
+        private AppCompatCameraPolicy getAppCompatCameraPolicy() {
+            return activity().top().mAppCompatController.getAppCompatCameraPolicy();
+        }
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java
index 2ef77f6..e1da913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java
@@ -61,4 +61,11 @@
     void enableUserAppAspectRatioSettings(boolean enabled) {
         doReturn(enabled).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
     }
+
+    void enableCameraCompatSplitScreenAspectRatio(boolean enabled) {
+        doReturn(enabled).when(mLetterboxConfiguration)
+                .isCameraCompatSplitScreenAspectRatioEnabled();
+    }
+
+
 }
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 b3f1502..564c29f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -245,8 +245,8 @@
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        doReturn(false).when(
-                mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+        doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+                .shouldRefreshActivityForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
         callOnActivityConfigurationChanging(mActivity);
@@ -271,7 +271,7 @@
     public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mLetterboxUiController)
+        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
                 .shouldRefreshActivityViaPauseForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -305,6 +305,7 @@
                 .build();
 
         spyOn(mActivity.mLetterboxUiController);
+        spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
         spyOn(mActivity.info);
 
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
@@ -315,12 +316,14 @@
 
     private void assertInCameraCompatMode() {
         assertNotEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE,
-                mActivity.mLetterboxUiController.getFreeformCameraCompatMode());
+                mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                        .getFreeformCameraCompatMode());
     }
 
     private void assertNotInCameraCompatMode() {
         assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE,
-                mActivity.mLetterboxUiController.getFreeformCameraCompatMode());
+                mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                        .getFreeformCameraCompatMode());
     }
 
     private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
@@ -329,8 +332,8 @@
 
     private void assertActivityRefreshRequested(boolean refreshRequested,
             boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
-                .setIsRefreshRequested(true);
+        verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+                times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
                 cycleThroughStop ? ON_STOP : ON_PAUSE);
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 c65371f..d7814ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -266,7 +266,7 @@
     public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(false).when(mActivity.mLetterboxUiController)
+        doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
                 .shouldForceRotateForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -468,8 +468,8 @@
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        doReturn(false).when(
-                mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+        doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+                .shouldRefreshActivityForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
         callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
@@ -494,8 +494,9 @@
     public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(false).when(mActivity.mLetterboxUiController)
-                .isCameraCompatSplitScreenAspectRatioAllowed();
+        doReturn(false).when(mActivity
+                        .mAppCompatController.getAppCompatCameraOverrides())
+                            .isCameraCompatSplitScreenAspectRatioAllowed();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
         callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
@@ -507,7 +508,7 @@
     public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mLetterboxUiController)
+        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
                 .isCameraCompatSplitScreenAspectRatioAllowed();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -533,7 +534,7 @@
     public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mLetterboxUiController)
+        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
                 .shouldRefreshActivityViaPauseForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -597,6 +598,7 @@
 
         spyOn(mActivity.mAtmService.getLifecycleManager());
         spyOn(mActivity.mLetterboxUiController);
+        spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
 
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
         doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
@@ -608,8 +610,8 @@
 
     private void assertActivityRefreshRequested(boolean refreshRequested,
                 boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
-                .setIsRefreshRequested(true);
+        verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+                times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
                 cycleThroughStop ? ON_STOP : ON_PAUSE);
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 bdd45c6..74e2d44 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -18,10 +18,6 @@
 
 import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
@@ -31,9 +27,6 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
@@ -45,7 +38,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 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.assertFalse;
@@ -54,8 +46,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -129,200 +119,7 @@
         mController = new LetterboxUiController(mWm, mActivity);
     }
 
-    // shouldRefreshActivityForCameraCompat
 
-    @Test
-    public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
-        doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-
-        assertFalse(mController.shouldRefreshActivityForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
-    public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-
-        assertFalse(mController.shouldRefreshActivityForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
-    public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mController.shouldRefreshActivityForCameraCompat());
-    }
-
-    @Test
-    public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mController.shouldRefreshActivityForCameraCompat());
-    }
-
-    @Test
-    public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertTrue(mController.shouldRefreshActivityForCameraCompat());
-    }
-
-    // shouldRefreshActivityViaPauseForCameraCompat
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
-    public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
-        doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-
-        assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
-    public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-
-        assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
-    public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
-    }
-
-    @Test
-    public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
-    }
-
-    // shouldForceRotateForCameraCompat
-
-    @Test
-    public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
-        doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-
-        assertFalse(mController.shouldForceRotateForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
-    public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-
-        assertFalse(mController.shouldForceRotateForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
-    public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mController.shouldForceRotateForCameraCompat());
-    }
-
-    @Test
-    public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mController.shouldForceRotateForCameraCompat());
-    }
-
-    @Test
-    public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue()
-            throws Exception {
-        doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertTrue(mController.shouldForceRotateForCameraCompat());
-    }
-
-    // shouldApplyFreeformTreatmentForCameraCompat
-
-    @Test
-    public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
-        mSetFlagsRule.disableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
-        assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
-        mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
-        assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
-    }
-
-    @Test
-    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse()
-            throws Exception {
-        mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
-    }
-
-    @Test
-    public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue()
-            throws Exception {
-        mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertTrue(mController.shouldApplyFreeformTreatmentForCameraCompat());
-    }
 
     @Test
     public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
@@ -641,39 +438,6 @@
         assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
     }
 
-    @Test
-    public void testRecomputeConfigurationForCameraCompatIfNeeded() {
-        spyOn(mController);
-        doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
-        doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
-        doReturn(false).when(mController).shouldOverrideMinAspectRatioForCamera();
-        clearInvocations(mActivity);
-
-        mController.recomputeConfigurationForCameraCompatIfNeeded();
-
-        verify(mActivity, never()).recomputeConfiguration();
-
-        // isOverrideOrientationOnlyForCameraEnabled
-        doReturn(true).when(mController).isOverrideOrientationOnlyForCameraEnabled();
-        clearInvocations(mActivity);
-        mController.recomputeConfigurationForCameraCompatIfNeeded();
-        verify(mActivity).recomputeConfiguration();
-
-        // isCameraCompatSplitScreenAspectRatioAllowed
-        doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
-        doReturn(true).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
-        clearInvocations(mActivity);
-        mController.recomputeConfigurationForCameraCompatIfNeeded();
-        verify(mActivity).recomputeConfiguration();
-
-        // shouldOverrideMinAspectRatioForCamera
-        doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
-        doReturn(true).when(mController).shouldOverrideMinAspectRatioForCamera();
-        clearInvocations(mActivity);
-        mController.recomputeConfigurationForCameraCompatIfNeeded();
-        verify(mActivity).recomputeConfiguration();
-    }
-
     private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
             boolean orientationRequest) {
         spyOn(mController);
@@ -875,7 +639,8 @@
         doReturn(true).when(mActivity).isCameraActive();
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+        assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
@@ -886,7 +651,8 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+        assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
@@ -897,7 +663,8 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
@@ -908,7 +675,8 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
@@ -917,7 +685,8 @@
         doReturn(true).when(mActivity).isCameraActive();
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
@@ -927,7 +696,8 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
@@ -938,7 +708,8 @@
         doReturn(true).when(mActivity).isCameraActive();
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+        assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index ef0aa9e..4bc87b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -2013,7 +2013,7 @@
     public void getTaskInfoPropagatesCameraCompatMode() {
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord activity = task.getTopMostActivity();
-        activity.mLetterboxUiController
+        activity.mAppCompatController.getAppCompatCameraOverrides()
                 .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT);
 
         assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT,
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl
new file mode 100644
index 0000000..fe46db8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.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 ProvisionSubscriberId;
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
new file mode 100644
index 0000000..796c82d
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+/**
+ * ProvisionSubscriberId
+ *
+ * Satellite Gateway client will use these subscriber ids to register with satellite gateway service
+ * which identify user subscription with unique subscriber ids. These subscriber ids can be any
+ * unique value like iccid, imsi or msisdn which is decided based upon carrier requirements.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public final class ProvisionSubscriberId implements Parcelable {
+    /** provision subscriberId */
+    @NonNull
+    private String mSubscriberId;
+
+    /** carrier id */
+    private int mCarrierId;
+
+    /**
+     * @hide
+     */
+    public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId) {
+        this.mCarrierId = carrierId;
+        this.mSubscriberId = subscriberId;
+    }
+
+    private ProvisionSubscriberId(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mSubscriberId);
+        out.writeInt(mCarrierId);
+    }
+
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final @android.annotation.NonNull Creator<ProvisionSubscriberId> CREATOR =
+            new Creator<ProvisionSubscriberId>() {
+                @Override
+                public ProvisionSubscriberId createFromParcel(Parcel in) {
+                    return new ProvisionSubscriberId(in);
+                }
+
+                @Override
+                public ProvisionSubscriberId[] newArray(int size) {
+                    return new ProvisionSubscriberId[size];
+                }
+            };
+
+    /**
+     * @hide
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @return token.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public String getSubscriberId() {
+        return mSubscriberId;
+    }
+
+    /**
+     * @return carrierId.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("SubscriberId:");
+        sb.append(mSubscriberId);
+        sb.append(",");
+
+        sb.append("CarrierId:");
+        sb.append(mCarrierId);
+        return sb.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSubscriberId, mCarrierId);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ProvisionSubscriberId that = (ProvisionSubscriberId) o;
+        return mSubscriberId.equals(that.mSubscriberId) && mCarrierId
+                == that.mCarrierId;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mSubscriberId = in.readString();
+        mCarrierId = in.readInt();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 6caed14..b518c60d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -50,6 +50,7 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -233,6 +234,29 @@
     public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
 
     /**
+     * Bundle key to get the response from
+     * {@link #requestProvisionSubscriberIds(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN =
+            "request_provision_subscriber_id";
+
+    /**
+     * Bundle key to get the response from
+     * {@link #requestIsProvisioned(String, Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_IS_SATELLITE_PROVISIONED = "request_is_satellite_provisioned";
+
+    /**
+     * Bundle key to get the response from
+     * {@link #provisionSatellite(List, Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
+
+
+    /**
      * The request was successfully processed.
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -2580,6 +2604,179 @@
         }
     }
 
+    /**
+     * Request to get list of prioritized satellite subscriber ids to be used for provision.
+     *
+     * Satellite Gateway client will use these subscriber ids to register with satellite gateway
+     * service which identify user subscription with unique subscriber ids. These subscriber ids
+     * can be any unique value like iccid, imsi or msisdn which is decided based upon carrier
+     * requirements.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered.
+     * If successful, the callback returns a list of tokens sorted in ascending priority order index
+     * 0 has the highest priority. Otherwise, it returns an error with a SatelliteException.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<List<ProvisionSubscriberId>, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
+                                List<ProvisionSubscriberId> list =
+                                        Collections.singletonList(resultData.getParcelable(
+                                                KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
+                                                ProvisionSubscriberId.class));
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(list)));
+                            } else {
+                                loge("KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestProvisionSubscriberIds(receiver);
+            } else {
+                loge("requestProvisionSubscriberIds() invalid telephony");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+            }
+        } catch (RemoteException ex) {
+            loge("requestProvisionSubscriberIds() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+        }
+    }
+
+    /**
+     * Request to get provisioned status for given a satellite subscriber id.
+     *
+     * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
+     * @param executor The executor on which the callback will be called.
+     * @param callback callback.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void requestIsProvisioned(@NonNull String satelliteSubscriberId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+        Objects.requireNonNull(satelliteSubscriberId);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
+                                boolean isIsProvisioned =
+                                        resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(isIsProvisioned)));
+                            } else {
+                                loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestIsProvisioned(satelliteSubscriberId, receiver);
+            } else {
+                loge("requestIsSatelliteProvisioned() invalid telephony");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+            }
+        } catch (RemoteException ex) {
+            loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+        }
+    }
+
+    /**
+     * Deliver the list of provisioned satellite subscriber ids.
+     *
+     * @param list List of ProvisionSubscriberId.
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void provisionSatellite(@NonNull List<ProvisionSubscriberId> list,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_PROVISION_SATELLITE_TOKENS)) {
+                                boolean isUpdated =
+                                        resultData.getBoolean(KEY_PROVISION_SATELLITE_TOKENS);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(isUpdated)));
+                            } else {
+                                loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.provisionSatellite(list, receiver);
+            } else {
+                loge("provisionSatellite() invalid telephony");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+            }
+        } catch (RemoteException ex) {
+            loge("provisionSatellite() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+        }
+    }
+
     @Nullable
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
new file mode 100644
index 0000000..2dc8ffb
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+/**
+ * {@hide}
+ */
+parcelable ProvisionSubscriberId {
+    /** provision subscriberId */
+    String subscriberId;
+
+    /** carrier id */
+    int mCarrierId;
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9b01b71..7be52ea 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -78,6 +78,7 @@
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.ProvisionSubscriberId;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IBooleanConsumer;
@@ -3400,4 +3401,38 @@
      * @hide
      */
     void requestSatelliteSessionStats(int subId, in ResultReceiver receiver);
+
+    /**
+     * Request to get list of prioritized satellite subscriber ids to be used for provision.
+     *
+     * @param result The result receiver, which returns the list of prioritized satellite tokens
+     * to be used for provision if the request is successful or an error code if the request failed.
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestProvisionSubscriberIds(in ResultReceiver receiver);
+
+    /**
+     * Request to get provisioned status for given a satellite subscriber id.
+     *
+     * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
+     * @param result The result receiver, which returns the provisioned status of the token if the
+     * request is successful or an error code if the request failed.
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result);
+
+    /**
+     * Deliver the list of provisioned satellite subscriber ids.
+     *
+     * @param list List of provisioned satellite subscriber ids.
+     * @param result The result receiver that returns whether deliver success or fail.
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void provisionSatellite(in List<ProvisionSubscriberId> list, in ResultReceiver result);
 }
diff --git a/tests/Input/res/drawable/test_key_drawable.xml b/tests/Input/res/drawable/test_key_drawable.xml
new file mode 100644
index 0000000..2addf8f
--- /dev/null
+++ b/tests/Input/res/drawable/test_key_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="4dp" />
+    <solid android:color="#ffffffff"/>
+</shape>
\ No newline at end of file
diff --git a/tests/Input/res/drawable/test_modifier_drawable.xml b/tests/Input/res/drawable/test_modifier_drawable.xml
new file mode 100644
index 0000000..2addf8f
--- /dev/null
+++ b/tests/Input/res/drawable/test_modifier_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="4dp" />
+    <solid android:color="#ffffffff"/>
+</shape>
\ No newline at end of file
diff --git a/tests/Input/res/xml/keyboard_glyph_maps.xml b/tests/Input/res/xml/keyboard_glyph_maps.xml
new file mode 100644
index 0000000..d0616ff
--- /dev/null
+++ b/tests/Input/res/xml/keyboard_glyph_maps.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<keyboard-glyph-maps xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <keyboard-glyph-map
+        androidprv:glyphMap="@xml/test_glyph_map"
+        androidprv:vendorId="0x1234"
+        androidprv:productId="0x3456" />
+</keyboard-glyph-maps>
\ No newline at end of file
diff --git a/tests/Input/res/xml/test_glyph_map.xml b/tests/Input/res/xml/test_glyph_map.xml
new file mode 100644
index 0000000..7a7c1ac
--- /dev/null
+++ b/tests/Input/res/xml/test_glyph_map.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<keyboard-glyph-map xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <key-glyph
+        androidprv:keycode="KEYCODE_BACK"
+        androidprv:glyphDrawable="@drawable/test_key_drawable" />
+    <modifier-glyph
+        androidprv:modifier="META"
+        androidprv:glyphDrawable="@drawable/test_modifier_drawable" />
+    <function-row-key androidprv:keycode="KEYCODE_EMOJI_PICKER" />
+    <hardware-defined-shortcut
+        androidprv:keycode="KEYCODE_1"
+        androidprv:modifierState="FUNCTION"
+        androidprv:outKeycode="KEYCODE_BACK" />
+    <hardware-defined-shortcut
+        androidprv:keycode="KEYCODE_2"
+        androidprv:modifierState="FUNCTION|META"
+        androidprv:outKeycode="KEYCODE_HOME" />
+</keyboard-glyph-map>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
new file mode 100644
index 0000000..c073c7a
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.input.KeyGlyphMap.KeyCombination
+import android.os.Bundle
+import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.hardware.input.Flags
+import com.android.test.input.R
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for custom keyboard glyph map configuration.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardGlyphManagerTests
+ */
+@Presubmit
+class KeyboardGlyphManagerTests {
+
+    companion object {
+        const val DEVICE_ID = 1
+        const val VENDOR_ID = 0x1234
+        const val PRODUCT_ID = 0x3456
+        const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
+        const val RECEIVER_NAME = "DummyReceiver"
+    }
+
+    @JvmField
+    @Rule(order = 0)
+    val setFlagsRule = SetFlagsRule()
+
+    @JvmField
+    @Rule(order = 1)
+    val mockitoRule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+
+    private lateinit var keyboardGlyphManager: KeyboardGlyphManager
+    private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+    private lateinit var keyboardDevice: InputDevice
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+        testLooper = TestLooper()
+        keyboardGlyphManager = KeyboardGlyphManager(context, testLooper.looper)
+
+        setupInputDevices()
+        setupBroadcastReceiver()
+        keyboardGlyphManager.systemRunning()
+        testLooper.dispatchAll()
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun setupInputDevices() {
+        val inputManager = InputManager(context)
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+            .thenReturn(inputManager)
+
+        keyboardDevice = createKeyboard(DEVICE_ID, VENDOR_ID, PRODUCT_ID, 0, "", "")
+        Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+    }
+
+    private fun setupBroadcastReceiver() {
+        Mockito.`when`(context.packageManager).thenReturn(packageManager)
+
+        val info = createMockReceiver()
+        Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
+            Mockito.anyInt())).thenReturn(listOf(info))
+        Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
+            .thenReturn(info.activityInfo)
+
+        val resources = context.resources
+        Mockito.`when`(
+            packageManager.getResourcesForApplication(
+                Mockito.any(
+                    ApplicationInfo::class.java
+                )
+            )
+        ).thenReturn(resources)
+    }
+
+    private fun createMockReceiver(): ResolveInfo {
+        val info = ResolveInfo()
+        info.activityInfo = ActivityInfo()
+        info.activityInfo.packageName = PACKAGE_NAME
+        info.activityInfo.name = RECEIVER_NAME
+        info.activityInfo.applicationInfo = ApplicationInfo()
+        info.activityInfo.metaData = Bundle()
+        info.activityInfo.metaData.putInt(
+            InputManager.META_DATA_KEYBOARD_GLYPH_MAPS,
+            R.xml.keyboard_glyph_maps
+        )
+        info.serviceInfo = ServiceInfo()
+        info.serviceInfo.packageName = PACKAGE_NAME
+        info.serviceInfo.name = RECEIVER_NAME
+        return info
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYBOARD_GLYPH_MAP)
+    fun testGlyphMapsLoaded() {
+        assertNotNull(
+            "Glyph map for test keyboard(deviceId=$DEVICE_ID) must exist",
+            keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID)
+        )
+        assertNull(
+            "Glyph map for non-existing keyboard must be null",
+            keyboardGlyphManager.getKeyGlyphMap(-2)
+        )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYBOARD_GLYPH_MAP)
+    fun testGlyphMapCorrectlyLoaded() {
+        val glyphMap = keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID)
+        // Test glyph map used in this test: {@see test_glyph_map.xml}
+        assertNotNull(glyphMap!!.getDrawableForKeycode(context, KeyEvent.KEYCODE_BACK))
+
+        assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_LEFT))
+        assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_RIGHT))
+
+        val functionRowKeys = glyphMap.functionRowKeys
+        assertEquals(1, functionRowKeys.size)
+        assertEquals(KeyEvent.KEYCODE_EMOJI_PICKER, functionRowKeys[0])
+
+        val hardwareShortcuts = glyphMap.hardwareShortcuts
+        assertEquals(2, hardwareShortcuts.size)
+        assertEquals(
+            KeyEvent.KEYCODE_BACK,
+            hardwareShortcuts[KeyCombination(KeyEvent.META_FUNCTION_ON, KeyEvent.KEYCODE_1)]
+        )
+        assertEquals(
+            KeyEvent.KEYCODE_HOME,
+            hardwareShortcuts[
+                KeyCombination(
+                    KeyEvent.META_FUNCTION_ON or KeyEvent.META_META_ON,
+                    KeyEvent.KEYCODE_2
+                )
+            ]
+        )
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 93f97cb..301c0e6 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -58,7 +58,7 @@
 import java.io.IOException
 import java.io.InputStream
 
-private fun createKeyboard(
+fun createKeyboard(
     deviceId: Int,
     vendorId: Int,
     productId: Int,
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index fff1dd1..0db0d95 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.inputmethod.multisessiontest;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
@@ -28,24 +30,33 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
+import android.app.UiAutomation;
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.core.app.ActivityScenario;
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
+import java.util.List;
+
 @RunWith(BedsteadJUnit4.class)
-@Ignore("b/345557347")
 public final class ConcurrentMultiUserTest {
 
     @ClassRule
@@ -55,6 +66,10 @@
     private static final ComponentName TEST_ACTIVITY = new ComponentName(
             getInstrumentation().getTargetContext().getPackageName(),
             MainActivity.class.getName());
+    private final Context mContext = getInstrumentation().getTargetContext();
+    private final InputMethodManager mInputMethodManager =
+            mContext.getSystemService(InputMethodManager.class);
+    private final UiAutomation mUiAutomation = getInstrumentation().getUiAutomation();
 
     private ActivityScenario<MainActivity> mActivityScenario;
     private MainActivity mActivity;
@@ -69,10 +84,12 @@
         // Launch driver activity.
         mActivityScenario = ActivityScenario.launch(MainActivity.class);
         mActivityScenario.onActivity(activity -> mActivity = activity);
+        mUiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_USERS_FULL);
     }
 
     @After
     public void tearDown() {
+        mUiAutomation.dropShellPermissionIdentity();
         if (mActivityScenario != null) {
             mActivityScenario.close();
         }
@@ -87,6 +104,58 @@
         assertPassengerImeHidden();
     }
 
+    @Test
+    public void imeListNotEmpty() {
+        List<InputMethodInfo> driverImeList = mInputMethodManager.getInputMethodList();
+        assertWithMessage("Driver IME list shouldn't be empty")
+                .that(driverImeList.isEmpty()).isFalse();
+
+        List<InputMethodInfo> passengerImeList =
+                mInputMethodManager.getInputMethodListAsUser(mPeerUserId);
+        assertWithMessage("Passenger IME list shouldn't be empty")
+                .that(passengerImeList.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void enabledImeListNotEmpty() {
+        List<InputMethodInfo> driverEnabledImeList =
+                mInputMethodManager.getEnabledInputMethodList();
+        assertWithMessage("Driver enabled IME list shouldn't be empty")
+                .that(driverEnabledImeList.isEmpty()).isFalse();
+
+        List<InputMethodInfo> passengerEnabledImeList =
+                mInputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(mPeerUserId));
+        assertWithMessage("Passenger enabled IME list shouldn't be empty")
+                .that(passengerEnabledImeList.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void currentImeNotNull() {
+        InputMethodInfo driverIme = mInputMethodManager.getCurrentInputMethodInfo();
+        assertWithMessage("Driver IME shouldn't be null").that(driverIme).isNotNull();
+
+        InputMethodInfo passengerIme =
+                mInputMethodManager.getCurrentInputMethodInfoAsUser(UserHandle.of(mPeerUserId));
+        assertWithMessage("Passenger IME shouldn't be null")
+                .that(passengerIme).isNotNull();
+    }
+
+    @Test
+    public void enableDisableImePerUser() throws IOException {
+        UserHandle driver = UserHandle.of(mContext.getUserId());
+        UserHandle passenger = UserHandle.of(mPeerUserId);
+        enableDisableImeForUser(driver, passenger);
+        enableDisableImeForUser(passenger, driver);
+    }
+
+    @Test
+    public void setImePerUser() throws IOException {
+        UserHandle driver = UserHandle.of(mContext.getUserId());
+        UserHandle passenger = UserHandle.of(mPeerUserId);
+        setImeForUser(driver, passenger);
+        setImeForUser(passenger, driver);
+    }
+
     private void assertDriverImeHidden() {
         assertWithMessage("Driver IME should be hidden")
                 .that(mActivity.isMyImeVisible()).isFalse();
@@ -104,4 +173,89 @@
     private void showDriverImeAndAssert() {
         mActivity.showMyImeAndWait();
     }
+
+    /**
+     * Disables/enables IME for {@code user1}, then verifies that the IME settings for {@code user1}
+     * has changed as expected and {@code user2} stays the same.
+     */
+    private void enableDisableImeForUser(UserHandle user1, UserHandle user2) throws IOException {
+        List<InputMethodInfo> user1EnabledImeList =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+        List<InputMethodInfo> user2EnabledImeList =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+
+        // Disable an IME for user1.
+        InputMethodInfo imeToDisable = user1EnabledImeList.get(0);
+        SystemUtil.runShellCommand(mUiAutomation,
+                "ime disable --user " + user1.getIdentifier() + " " + imeToDisable.getId());
+        List<InputMethodInfo> user1EnabledImeList2 =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+        List<InputMethodInfo> user2EnabledImeList2 =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+        assertWithMessage("User " + user1.getIdentifier() + " IME " + imeToDisable.getId()
+                + " should be disabled")
+                .that(user1EnabledImeList2.contains(imeToDisable)).isFalse();
+        assertWithMessage("Disabling user " + user1.getIdentifier()
+                + " IME shouldn't affect user " + user2.getIdentifier())
+                .that(user2EnabledImeList2.containsAll(user2EnabledImeList)
+                        && user2EnabledImeList.containsAll(user2EnabledImeList2))
+                .isTrue();
+
+        // Enable the IME.
+        SystemUtil.runShellCommand(mUiAutomation,
+                "ime enable --user " + user1.getIdentifier() + " " + imeToDisable.getId());
+        List<InputMethodInfo> user1EnabledImeList3 =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+        List<InputMethodInfo> user2EnabledImeList3 =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+        assertWithMessage("User " + user1.getIdentifier() + " IME " + imeToDisable.getId()
+                + " should be enabled").that(user1EnabledImeList3.contains(imeToDisable)).isTrue();
+        assertWithMessage("Enabling user " + user1.getIdentifier()
+                + " IME shouldn't affect user " + user2.getIdentifier())
+                .that(user2EnabledImeList2.containsAll(user2EnabledImeList3)
+                        && user2EnabledImeList3.containsAll(user2EnabledImeList2))
+                .isTrue();
+    }
+
+    /**
+     * Sets/resets IME for {@code user1}, then verifies that the IME settings for {@code user1}
+     * has changed as expected and {@code user2} stays the same.
+     */
+    private void setImeForUser(UserHandle user1, UserHandle user2) throws IOException {
+        // Reset IME for user1.
+        SystemUtil.runShellCommand(mUiAutomation,
+                "ime reset --user " + user1.getIdentifier());
+
+        List<InputMethodInfo> user1EnabledImeList =
+                mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+        assumeTrue("There must be at least two IME to test", user1EnabledImeList.size() >= 2);
+        InputMethodInfo user1Ime = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+        InputMethodInfo user2Ime = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+
+        // Set to another IME for user1.
+        InputMethodInfo anotherIme = null;
+        for (InputMethodInfo info : user1EnabledImeList) {
+            if (!info.equals(user1Ime)) {
+                anotherIme = info;
+            }
+        }
+        SystemUtil.runShellCommand(mUiAutomation,
+                "ime set --user " + user1.getIdentifier() + " " + anotherIme.getId());
+        InputMethodInfo user1Ime2 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+        InputMethodInfo user2Ime2 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+        assertWithMessage("The current IME for user " + user1.getIdentifier() + " is wrong")
+                .that(user1Ime2).isEqualTo(anotherIme);
+        assertWithMessage("The current IME for user " + user2.getIdentifier() + " shouldn't change")
+                .that(user2Ime2).isEqualTo(user2Ime);
+
+        // Reset IME for user1.
+        SystemUtil.runShellCommand(mUiAutomation,
+                "ime reset --user " + user1.getIdentifier());
+        InputMethodInfo user1Ime3 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+        InputMethodInfo user2Ime3 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+        assertWithMessage("The current IME for user " + user1.getIdentifier() + " is wrong")
+                .that(user1Ime3).isEqualTo(user1Ime);
+        assertWithMessage("The current IME for user " + user2.getIdentifier() + " shouldn't change")
+                .that(user2Ime3).isEqualTo(user2Ime);
+    }
 }
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index f126000..3c97b79 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -87,11 +87,13 @@
             }
             if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
                 Log.e(TAG, String.format("Failed to show my IME as user %d, "
-                                + "mEditor:focused=%b,hasWindowFocus=%b", getUserId(),
+                                + "mEditor:focused=%b,hasWindowFocus=%b",
+                        Process.myUserHandle().getIdentifier(),
                         mEditor.isFocused(), mEditor.hasWindowFocus()));
             }
         });
         PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
-                String.format("My IME (user %d) didn't show up", getUserId()));
+                String.format("My IME (user %d) didn't show up",
+                        Process.myUserHandle().getIdentifier()));
     }
 }