Merge "Revert^2 "Defer destroy activity if it is in transition."" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b42f7bc..e857175 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,7 @@
 [Builtin Hooks]
 clang_format = true
 bpfmt = true
+ktfmt = true
 
 [Builtin Hooks Options]
 # Only turn on clang-format check for the following subfolders.
@@ -17,6 +18,7 @@
                tests/
                tools/
 bpfmt = -d
+ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
@@ -25,9 +27,10 @@
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
-ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
-
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
 
 # This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
 flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
+
+[Tool Paths]
+ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 96315eb..50d97cf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14435,6 +14435,7 @@
     method @NonNull public android.telephony.CarrierRestrictionRules build();
     method @NonNull public android.telephony.CarrierRestrictionRules.Builder setAllCarriersAllowed();
     method @NonNull public android.telephony.CarrierRestrictionRules.Builder setAllowedCarriers(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>);
+    method @FlaggedApi("com.android.internal.telephony.flags.set_carrier_restriction_status") @NonNull public android.telephony.CarrierRestrictionRules.Builder setCarrierRestrictionStatus(int);
     method @NonNull public android.telephony.CarrierRestrictionRules.Builder setDefaultCarrierRestriction(int);
     method @NonNull public android.telephony.CarrierRestrictionRules.Builder setExcludedCarriers(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>);
     method @NonNull public android.telephony.CarrierRestrictionRules.Builder setMultiSimPolicy(int);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index fd9600c..65628d3 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,6 +16,7 @@
 
 package android.accessibilityservice;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION;
 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
@@ -69,6 +70,8 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.inputmethod.EditorInfo;
 
+import androidx.annotation.GuardedBy;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
@@ -640,6 +643,8 @@
         /** The detected gesture information for different displays */
         boolean onGesture(AccessibilityGestureEvent gestureInfo);
         boolean onKeyEvent(KeyEvent event);
+        /** Magnification SystemUI connection changed callbacks */
+        void onMagnificationSystemUIConnectionChanged(boolean connected);
         /** Magnification changed callbacks for different displays */
         void onMagnificationChanged(int displayId, @NonNull Region region,
                 MagnificationConfig config);
@@ -790,7 +795,6 @@
     public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
             "screenshot_timestamp";
 
-
     /**
      * Annotations for result codes of attaching accessibility overlays.
      *
@@ -837,6 +841,13 @@
 
     private WindowManager mWindowManager;
 
+    @GuardedBy("mLock")
+    private boolean mServiceConnected;
+    @GuardedBy("mLock")
+    private boolean mMagnificationSystemUIConnected;
+    @GuardedBy("mLock")
+    private boolean mServiceConnectedNotified;
+
     /** List of magnification controllers, mapping from displayId -> MagnificationController. */
     private final SparseArray<MagnificationController> mMagnificationControllers =
             new SparseArray<>(0);
@@ -886,11 +897,14 @@
             for (int i = 0; i < mMagnificationControllers.size(); i++) {
                 mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
             }
+            checkIsMagnificationSystemUIConnectedAlready();
             final AccessibilityServiceInfo info = getServiceInfo();
             if (info != null) {
                 updateInputMethod(info);
                 mMotionEventSources = info.getMotionEventSources();
             }
+            mServiceConnected = true;
+            mServiceConnectedNotified = false;
         }
         if (mSoftKeyboardController != null) {
             mSoftKeyboardController.onServiceConnected();
@@ -898,7 +912,57 @@
 
         // The client gets to handle service connection last, after we've set
         // up any state upon which their code may rely.
-        onServiceConnected();
+        if (android.view.accessibility.Flags
+                .waitMagnificationSystemUiConnectionToNotifyServiceConnected()) {
+            notifyOnServiceConnectedIfReady();
+        } else {
+            onServiceConnected();
+        }
+    }
+
+    private void notifyOnServiceConnectedIfReady() {
+        synchronized (mLock) {
+            if (mServiceConnectedNotified) {
+                return;
+            }
+            boolean canControlMagnification;
+            final AccessibilityServiceInfo info = getServiceInfo();
+            if (info != null) {
+                int flagMask = CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+                canControlMagnification = (info.getCapabilities() & flagMask) == flagMask;
+            } else {
+                canControlMagnification = false;
+            }
+            boolean ready = canControlMagnification
+                    ? (mServiceConnected && mMagnificationSystemUIConnected)
+                    : mServiceConnected;
+            if (ready) {
+                getMainExecutor().execute(() -> onServiceConnected());
+                mServiceConnectedNotified = true;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void checkIsMagnificationSystemUIConnectedAlready() {
+        if (!android.view.accessibility.Flags
+                .waitMagnificationSystemUiConnectionToNotifyServiceConnected()) {
+            return;
+        }
+        if (mMagnificationSystemUIConnected) {
+            return;
+        }
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                boolean connected = connection.isMagnificationSystemUIConnected();
+                mMagnificationSystemUIConnected = connected;
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Failed to check magnification system ui connection", re);
+                re.rethrowFromSystemServer();
+            }
+        }
     }
 
     private void updateInputMethod(AccessibilityServiceInfo info) {
@@ -1360,6 +1424,22 @@
         }
     }
 
+    private void onMagnificationSystemUIConnectionChanged(boolean connected) {
+        if (!android.view.accessibility.Flags
+                .waitMagnificationSystemUiConnectionToNotifyServiceConnected()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            boolean changed = (mMagnificationSystemUIConnected != connected);
+            mMagnificationSystemUIConnected = connected;
+
+            if (changed) {
+                notifyOnServiceConnectedIfReady();
+            }
+        }
+    }
+
     private void onMagnificationChanged(int displayId, @NonNull Region region,
             MagnificationConfig config) {
         MagnificationController controller;
@@ -2823,6 +2903,11 @@
             }
 
             @Override
+            public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+                AccessibilityService.this.onMagnificationSystemUIConnectionChanged(connected);
+            }
+
+            @Override
             public void onMagnificationChanged(int displayId, @NonNull Region region,
                     MagnificationConfig config) {
                 AccessibilityService.this.onMagnificationChanged(displayId, region, config);
@@ -3032,6 +3117,16 @@
             });
         }
 
+        @Override
+        public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+            mExecutor.execute(() -> {
+                if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                    mCallback.onMagnificationSystemUIConnectionChanged(connected);
+                }
+                return;
+            });
+        }
+
         /** Magnification changed callbacks for different displays */
         public void onMagnificationChanged(int displayId, @NonNull Region region,
                 MagnificationConfig config) {
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 3bc61e5..f1479ef 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -48,6 +48,8 @@
 
     void onKeyEvent(in KeyEvent event, int sequence);
 
+    void onMagnificationSystemUIConnectionChanged(boolean connected);
+
     void onMagnificationChanged(int displayId, in Region region, in MagnificationConfig config);
 
     void onMotionEvent(in MotionEvent event);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 713d8e5..149e719 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -130,6 +130,9 @@
     void setMagnificationCallbackEnabled(int displayId, boolean enabled);
 
     @RequiresNoPermission
+    boolean isMagnificationSystemUIConnected();
+
+    @RequiresNoPermission
     boolean setSoftKeyboardShowMode(int showMode);
 
     @RequiresNoPermission
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2887d22..fa8fe3b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1755,6 +1755,12 @@
         private int mNavigationBarColor;
         @Appearance
         private int mSystemBarsAppearance;
+        /**
+         * Similar to {@link TaskDescription#mSystemBarsAppearance}, but is taken from the topmost
+         * fully opaque (i.e. non transparent) activity in the task.
+         */
+        @Appearance
+        private int mTopOpaqueSystemBarsAppearance;
         private boolean mEnsureStatusBarContrastWhenTransparent;
         private boolean mEnsureNavigationBarContrastWhenTransparent;
         private int mResizeMode;
@@ -1855,7 +1861,7 @@
                 final Icon icon = mIconRes == Resources.ID_NULL ? null :
                         Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
                 return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
-                        mStatusBarColor, mNavigationBarColor, 0, false, false,
+                        mStatusBarColor, mNavigationBarColor, 0, 0, false, false,
                         RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             }
         }
@@ -1874,7 +1880,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    colorPrimary, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1892,7 +1898,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1904,7 +1910,7 @@
          */
         @Deprecated
         public TaskDescription(String label) {
-            this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1914,7 +1920,7 @@
          */
         @Deprecated
         public TaskDescription() {
-            this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(null, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1930,7 +1936,7 @@
         @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
             this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
-                    0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1946,7 +1952,7 @@
          */
         @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, 0, false,
                     false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
@@ -1955,6 +1961,7 @@
                 int colorPrimary, int colorBackground,
                 int statusBarColor, int navigationBarColor,
                 @Appearance int systemBarsAppearance,
+                @Appearance int topOpaqueSystemBarsAppearance,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
                 int minHeight, int colorBackgroundFloating) {
@@ -1965,6 +1972,7 @@
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
             mSystemBarsAppearance = systemBarsAppearance;
+            mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance;
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     ensureNavigationBarContrastWhenTransparent;
@@ -1994,6 +2002,7 @@
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
             mSystemBarsAppearance = other.mSystemBarsAppearance;
+            mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance;
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     other.mEnsureNavigationBarContrastWhenTransparent;
@@ -2026,6 +2035,9 @@
             if (other.mSystemBarsAppearance != 0) {
                 mSystemBarsAppearance = other.mSystemBarsAppearance;
             }
+            if (other.mTopOpaqueSystemBarsAppearance != 0) {
+                mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance;
+            }
 
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
@@ -2305,6 +2317,14 @@
         /**
          * @hide
          */
+        @Appearance
+        public int getTopOpaqueSystemBarsAppearance() {
+            return mTopOpaqueSystemBarsAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public void setEnsureStatusBarContrastWhenTransparent(
                 boolean ensureStatusBarContrastWhenTransparent) {
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2320,6 +2340,13 @@
         /**
          * @hide
          */
+        public void setTopOpaqueSystemBarsAppearance(int topOpaqueSystemBarsAppearance) {
+            mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public boolean getEnsureNavigationBarContrastWhenTransparent() {
             return mEnsureNavigationBarContrastWhenTransparent;
         }
@@ -2442,6 +2469,7 @@
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
             dest.writeInt(mSystemBarsAppearance);
+            dest.writeInt(mTopOpaqueSystemBarsAppearance);
             dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
             dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             dest.writeInt(mResizeMode);
@@ -2466,6 +2494,7 @@
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
             mSystemBarsAppearance = source.readInt();
+            mTopOpaqueSystemBarsAppearance = source.readInt();
             mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
             mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mResizeMode = source.readInt();
@@ -2498,7 +2527,8 @@
                     + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
                     + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
                     + " colorBackgrounFloating: " + mColorBackgroundFloating
-                    + " systemBarsAppearance: " + mSystemBarsAppearance;
+                    + " systemBarsAppearance: " + mSystemBarsAppearance
+                    + " topOpaqueSystemBarsAppearance: " + mTopOpaqueSystemBarsAppearance;
         }
 
         @Override
@@ -2519,6 +2549,7 @@
             result = result * 31 + mStatusBarColor;
             result = result * 31 + mNavigationBarColor;
             result = result * 31 + mSystemBarsAppearance;
+            result = result * 31 + mTopOpaqueSystemBarsAppearance;
             result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0);
             result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0);
             result = result * 31 + mResizeMode;
@@ -2542,6 +2573,7 @@
                     && mStatusBarColor == other.mStatusBarColor
                     && mNavigationBarColor == other.mNavigationBarColor
                     && mSystemBarsAppearance == other.mSystemBarsAppearance
+                    && mTopOpaqueSystemBarsAppearance == other.mTopOpaqueSystemBarsAppearance
                     && mEnsureStatusBarContrastWhenTransparent
                             == other.mEnsureStatusBarContrastWhenTransparent
                     && mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index c0f7232..5956e2b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2617,6 +2617,9 @@
         try {
             Objects.requireNonNull(packageName);
             return mPM.isAppArchivable(packageName, new UserHandle(getUserId()));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(NameNotFoundException.class);
+            throw new RuntimeException(e);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4c839f1..fc3bb02 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -114,7 +114,7 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.internal.util.NewlineNormalizer;
+import com.android.internal.util.NotificationBigTextNormalizer;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1632,6 +1632,10 @@
     private Icon mSmallIcon;
     @UnsupportedAppUsage
     private Icon mLargeIcon;
+    private Icon mAppIcon;
+
+    /** Cache for whether the notification was posted by a headless system app. */
+    private Boolean mBelongsToHeadlessSystemApp = null;
 
     @UnsupportedAppUsage
     private String mChannelId;
@@ -3079,25 +3083,17 @@
                     return name.toString();
                 }
             }
-            // If not, try getting the app info from extras.
+            // If not, try getting the name from the app info.
             if (context == null) {
                 return null;
             }
-            final PackageManager pm = context.getPackageManager();
             if (TextUtils.isEmpty(name)) {
-                if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
-                    final ApplicationInfo info = extras.getParcelable(
-                            EXTRA_BUILDER_APPLICATION_INFO,
-                            ApplicationInfo.class);
-                    if (info != null) {
-                        name = pm.getApplicationLabel(info);
-                    }
+                ApplicationInfo info = getApplicationInfo(context);
+                if (info != null) {
+                    final PackageManager pm = context.getPackageManager();
+                    name = pm.getApplicationLabel(getApplicationInfo(context));
                 }
             }
-            // If that's still empty, use the one from the context directly.
-            if (TextUtils.isEmpty(name)) {
-                name = pm.getApplicationLabel(context.getApplicationInfo());
-            }
             // If there's still nothing, ¯\_(ツ)_/¯
             if (TextUtils.isEmpty(name)) {
                 return null;
@@ -3109,9 +3105,89 @@
     }
 
     /**
+     * Whether this notification was posted by a headless system app.
+     *
+     * If we don't have enough information to figure this out, this will return false. Therefore,
+     * false negatives are possible, but false positives should not be.
+     *
      * @hide
      */
-    public int loadHeaderAppIconRes(Context context) {
+    public boolean belongsToHeadlessSystemApp(Context context) {
+        Trace.beginSection("Notification#belongsToHeadlessSystemApp");
+
+        try {
+            if (mBelongsToHeadlessSystemApp != null) {
+                return mBelongsToHeadlessSystemApp;
+            }
+
+            if (context == null) {
+                // Without a valid context, we don't know exactly. Let's assume it doesn't belong to
+                // a system app, but not cache the value.
+                return false;
+            }
+
+            ApplicationInfo info = getApplicationInfo(context);
+            if (info != null) {
+                if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    // It's not a system app at all.
+                    mBelongsToHeadlessSystemApp = false;
+                } else {
+                    // If there's no launch intent, it's probably a headless app.
+                    final PackageManager pm = context.getPackageManager();
+                    mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName)
+                            == null;
+                }
+            } else {
+                // If for some reason we don't have the app info, we don't know; best assume it's
+                // not a system app.
+                return false;
+            }
+            return mBelongsToHeadlessSystemApp;
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    /**
+     * Get the resource ID of the app icon from application info.
+     * @hide
+     */
+    public int getHeaderAppIconRes(Context context) {
+        ApplicationInfo info = getApplicationInfo(context);
+        if (info != null) {
+            return info.icon;
+        }
+        return 0;
+    }
+
+    /**
+     * Load the app icon drawable from the package manager. This could result in a binder call.
+     * @hide
+     */
+    public Drawable loadHeaderAppIcon(Context context) {
+        Trace.beginSection("Notification#loadHeaderAppIcon");
+
+        try {
+            if (context == null) {
+                Log.e(TAG, "Cannot load the app icon drawable with a null context");
+                return null;
+            }
+            final PackageManager pm = context.getPackageManager();
+            ApplicationInfo info = getApplicationInfo(context);
+            if (info == null) {
+                Log.e(TAG, "Cannot load the app icon drawable: no application info");
+                return null;
+            }
+            return pm.getApplicationIcon(info);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    /**
+     * Fetch the application info from the notification, or the context if that isn't available.
+     */
+    private ApplicationInfo getApplicationInfo(Context context) {
         ApplicationInfo info = null;
         if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
             info = extras.getParcelable(
@@ -3119,12 +3195,12 @@
                     ApplicationInfo.class);
         }
         if (info == null) {
+            if (context == null) {
+                return null;
+            }
             info = context.getApplicationInfo();
         }
-        if (info != null) {
-            return info.icon;
-        }
-        return 0;
+        return info;
     }
 
     /**
@@ -3186,12 +3262,12 @@
         return cs.toString();
     }
 
-    private static CharSequence cleanUpNewLines(@Nullable CharSequence charSequence) {
+    private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) {
         if (charSequence == null) {
             return charSequence;
         }
 
-        return NewlineNormalizer.normalizeNewlines(charSequence.toString());
+        return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString());
     }
 
     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -4124,6 +4200,55 @@
     }
 
     /**
+     * The colored app icon that can replace the small icon in the notification starting in V.
+     *
+     * Before using this value, you should first check whether it's actually being used by the
+     * notification by calling {@link Notification#shouldUseAppIcon()}.
+     *
+     * @hide
+     */
+    public Icon getAppIcon() {
+        if (mAppIcon != null) {
+            return mAppIcon;
+        }
+        // If the app icon hasn't been loaded yet, check if we can load it without a context.
+        if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+            final ApplicationInfo info = extras.getParcelable(
+                    EXTRA_BUILDER_APPLICATION_INFO,
+                    ApplicationInfo.class);
+            if (info != null) {
+                int appIconRes = info.icon;
+                if (appIconRes == 0) {
+                    Log.w(TAG, "Failed to get the app icon: no icon in application info");
+                    return null;
+                }
+                mAppIcon = Icon.createWithResource(info.packageName, appIconRes);
+                return mAppIcon;
+            } else {
+                Log.e(TAG, "Failed to get the app icon: "
+                        + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null");
+            }
+        } else {
+            Log.w(TAG, "Failed to get the app icon: no application info in extras");
+        }
+        return null;
+    }
+
+    /**
+     * Whether the notification is using the app icon instead of the small icon.
+     * @hide
+     */
+    public boolean shouldUseAppIcon() {
+        if (Flags.notificationsUseAppIconInRow()) {
+            if (belongsToHeadlessSystemApp(/* context = */ null)) {
+                return false;
+            }
+            return getAppIcon() != null;
+        }
+        return false;
+    }
+
+    /**
      * The large icon shown in this notification's content view.
      * @see Builder#getLargeIcon()
      * @see Builder#setLargeIcon(Icon)
@@ -6116,16 +6241,30 @@
             if (Flags.notificationsUseAppIcon()) {
                 // Override small icon with app icon
                 mN.mSmallIcon = Icon.createWithResource(mContext,
-                        mN.loadHeaderAppIconRes(mContext));
+                        mN.getHeaderAppIconRes(mContext));
             } else if (mN.mSmallIcon == null && mN.icon != 0) {
                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
             }
 
-            contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+            boolean usingAppIcon = false;
+            if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) {
+                // Use the app icon in the view
+                int appIconRes = mN.getHeaderAppIconRes(mContext);
+                if (appIconRes != 0) {
+                    mN.mAppIcon = Icon.createWithResource(mContext, appIconRes);
+                    contentView.setImageViewIcon(R.id.icon, mN.mAppIcon);
+                    usingAppIcon = true;
+                } else {
+                    Log.w(TAG, "bindSmallIcon: could not get the app icon");
+                }
+            }
+            if (!usingAppIcon) {
+                contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+            }
             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
 
             // Don't change color if we're using the app icon.
-            if (!Flags.notificationsUseAppIcon()) {
+            if (!Flags.notificationsUseAppIcon() && !usingAppIcon) {
                 processSmallIconColor(mN.mSmallIcon, contentView, p);
             }
         }
@@ -8427,7 +8566,7 @@
             // Replace the text with the big text, but only if the big text is not empty.
             CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
             if (Flags.cleanUpSpansAndNewLines()) {
-                bigTextText = cleanUpNewLines(stripStyling(bigTextText));
+                bigTextText = normalizeBigText(stripStyling(bigTextText));
             }
             if (!TextUtils.isEmpty(bigTextText)) {
                 p.text(bigTextText);
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 348d4d8f..273a79e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1969,6 +1969,11 @@
                 }
 
                 @Override
+                public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+                    /* do nothing */
+                }
+
+                @Override
                 public void onMagnificationChanged(int displayId, @NonNull Region region,
                         MagnificationConfig config) {
                     /* do nothing */
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 55c3bb60..6edae0b 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -52,14 +52,32 @@
   bug: "281044385"
 }
 
+# vvv Prototypes for using app icons in notifications vvv
+
 flag {
   name: "notifications_use_app_icon"
   namespace: "systemui"
-  description: "Experiment to replace the small icon in a notification with the app icon."
+  description: "Experiment to replace the small icon in a notification with the app icon. This includes the status bar, AOD, shelf and notification row itself."
   bug: "335211019"
 }
 
 flag {
+  name: "notifications_use_app_icon_in_row"
+  namespace: "systemui"
+  description: "Experiment to replace the small icon in a notification row with the app icon."
+  bug: "335211019"
+}
+
+flag {
+  name: "notifications_use_monochrome_app_icon"
+  namespace: "systemui"
+  description: "Experiment to replace the notification icon in the status bar and shelf with the monochrome app icon, if available."
+  bug: "335211019"
+}
+
+# ^^^ Prototypes for using app icons in notifications ^^^
+
+flag {
   name: "notification_expansion_optional"
   namespace: "systemui"
   description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions."
diff --git a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java
index 1c8e497..eb31db1 100644
--- a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java
@@ -27,6 +27,8 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Objects;
 
 /**
@@ -38,7 +40,9 @@
     private static final String TAG = "WindowStateInsetsControlChangeItem";
 
     private InsetsState mInsetsState;
-    private InsetsSourceControl.Array mActiveControls;
+
+    @VisibleForTesting
+    public InsetsSourceControl.Array mActiveControls;
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull IWindow window,
@@ -51,6 +55,8 @@
             // An exception could happen if the process is restarted. It is safe to ignore since
             // the window should no longer exist.
             Log.w(TAG, "The original window no longer exists in the new process", e);
+            // Prevent leak
+            mActiveControls.release();
         }
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
@@ -69,7 +75,12 @@
         }
         instance.setWindow(window);
         instance.mInsetsState = new InsetsState(insetsState, true /* copySources */);
-        instance.mActiveControls = new InsetsSourceControl.Array(activeControls);
+        instance.mActiveControls = new InsetsSourceControl.Array(
+                activeControls, true /* copyControls */);
+        // This source control is an extra copy if the client is not local. By setting
+        // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of
+        // SurfaceControl.writeToParcel.
+        instance.mActiveControls.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
 
         return instance;
     }
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 374be6f..18cfca6 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -40,3 +40,13 @@
   description: "Throttle the widget view updates to mitigate transaction exceptions"
   bug: "326145514"
 }
+
+flag {
+  name: "support_resume_restore_after_reboot"
+  namespace: "app_widgets"
+  description: "Enable support for resume restore widget after reboot by persisting intermediate states to disk"
+  bug: "336976070"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index ed5d662..1e781532 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -64,3 +64,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "virtual_devices"
+    name: "intent_interception_action_matching_fix"
+    description: "Do not match intents without actions if the filter has actions"
+    bug: "343805037"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 270fc32..bbd0e9f 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2431,6 +2431,7 @@
                     statusReceiver, new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
+            throw new RuntimeException(e);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2467,6 +2468,7 @@
         } catch (ParcelableException e) {
             e.maybeRethrow(IOException.class);
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
+            throw new RuntimeException(e);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2499,6 +2501,7 @@
                     userActionIntent, new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
+            throw new RuntimeException(e);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e3780ed..f54be00 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -733,7 +733,7 @@
      * commits, or is rolled back, either explicitly or by a call to
      * {@link #yieldIfContendedSafely}.
      */
-    // TODO(340874899) Provide an Executor overload
+    @SuppressLint("ExecutorRegistration")
     public void beginTransactionWithListener(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, true);
@@ -763,7 +763,7 @@
      *            transaction begins, commits, or is rolled back, either
      *            explicitly or by a call to {@link #yieldIfContendedSafely}.
      */
-    // TODO(340874899) Provide an Executor overload
+    @SuppressLint("ExecutorRegistration")
     public void beginTransactionWithListenerNonExclusive(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, false);
@@ -789,7 +789,6 @@
      *   }
      * </pre>
      */
-    // TODO(340874899) Provide an Executor overload
     @SuppressLint("ExecutorRegistration")
     @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionWithListenerReadOnly(
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 4284ad0..047d1fa 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -32,3 +32,10 @@
   description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder."
   bug: "302735104"
 }
+
+flag {
+  name: "mandatory_biometrics"
+  namespace: "biometrics_framework"
+  description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
+  bug: "322081563"
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 342479b..3cc87ea 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1492,10 +1492,12 @@
     /**
      * <p>Default flash brightness level for manual flash control in SINGLE mode.</p>
      * <p>If flash unit is available this will be greater than or equal to 1 and less
-     * or equal to <code>android.flash.info.singleStrengthMaxLevel</code>.
+     * or equal to {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}.
      * Note for devices that do not support the manual flash strength control
      * feature, this level will always be equal to 1.</p>
      * <p>This key is available on all devices.</p>
+     *
+     * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
      */
     @PublicKey
     @NonNull
@@ -1511,13 +1513,15 @@
      * otherwise the value will be equal to 1.</p>
      * <p>Note that this level is just a number of supported levels(the granularity of control).
      * There is no actual physical power units tied to this level.
-     * There is no relation between android.flash.info.torchStrengthMaxLevel and
-     * android.flash.info.singleStrengthMaxLevel i.e. the ratio of
-     * android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel
+     * There is no relation between {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} and
+     * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} i.e. the ratio of
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}:{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}
      * is not guaranteed to be the ratio of actual brightness.</p>
      * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#FLASH_MODE
+     * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+     * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
      */
     @PublicKey
     @NonNull
@@ -1528,10 +1532,12 @@
     /**
      * <p>Default flash brightness level for manual flash control in TORCH mode</p>
      * <p>If flash unit is available this will be greater than or equal to 1 and less
-     * or equal to android.flash.info.torchStrengthMaxLevel.
+     * or equal to {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}.
      * Note for the devices that do not support the manual flash strength control feature,
      * this level will always be equal to 1.</p>
      * <p>This key is available on all devices.</p>
+     *
+     * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c82e7e8..938636f 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2684,35 +2684,39 @@
      * <p>Flash strength level to be used when manual flash control is active.</p>
      * <p>Flash strength level to use in capture mode i.e. when the applications control
      * flash with either SINGLE or TORCH mode.</p>
-     * <p>Use android.flash.info.singleStrengthMaxLevel and
-     * android.flash.info.torchStrengthMaxLevel to check whether the device supports
+     * <p>Use {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} to check whether the device supports
      * flash strength control or not.
      * If the values of android.flash.info.singleStrengthMaxLevel and
-     * android.flash.info.torchStrengthMaxLevel are greater than 1,
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1,
      * then the device supports manual flash strength control.</p>
      * <p>If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> TORCH the value must be &gt;= 1
-     * and &lt;= android.flash.info.torchStrengthMaxLevel.
+     * and &lt;= {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}.
      * If the application doesn't set the key and
-     * android.flash.info.torchStrengthMaxLevel &gt; 1,
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} &gt; 1,
      * then the flash will be fired at the default level set by HAL in
-     * android.flash.info.torchStrengthDefaultLevel.
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL android.flash.torchStrengthDefaultLevel}.
      * If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> SINGLE, then the value must be &gt;= 1
-     * and &lt;= android.flash.info.singleStrengthMaxLevel.
+     * and &lt;= {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}.
      * If the application does not set this key and
-     * android.flash.info.singleStrengthMaxLevel &gt; 1,
+     * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} &gt; 1,
      * then the flash will be fired at the default level set by HAL
-     * in android.flash.info.singleStrengthDefaultLevel.
+     * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}.
      * If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of ON_AUTO_FLASH, ON_ALWAYS_FLASH,
      * ON_AUTO_FLASH_REDEYE, ON_EXTERNAL_FLASH values, then the strengthLevel will be ignored.</p>
      * <p><b>Range of valid values:</b><br>
-     * <code>[1-android.flash.info.torchStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+     * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to TORCH;
-     * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+     * <code>[1-{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to SINGLE</p>
      * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#FLASH_MODE
+     * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL
+     * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+     * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL
+     * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1460515..4406a41 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2977,35 +2977,39 @@
      * <p>Flash strength level to be used when manual flash control is active.</p>
      * <p>Flash strength level to use in capture mode i.e. when the applications control
      * flash with either SINGLE or TORCH mode.</p>
-     * <p>Use android.flash.info.singleStrengthMaxLevel and
-     * android.flash.info.torchStrengthMaxLevel to check whether the device supports
+     * <p>Use {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} to check whether the device supports
      * flash strength control or not.
      * If the values of android.flash.info.singleStrengthMaxLevel and
-     * android.flash.info.torchStrengthMaxLevel are greater than 1,
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1,
      * then the device supports manual flash strength control.</p>
      * <p>If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> TORCH the value must be &gt;= 1
-     * and &lt;= android.flash.info.torchStrengthMaxLevel.
+     * and &lt;= {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}.
      * If the application doesn't set the key and
-     * android.flash.info.torchStrengthMaxLevel &gt; 1,
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} &gt; 1,
      * then the flash will be fired at the default level set by HAL in
-     * android.flash.info.torchStrengthDefaultLevel.
+     * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL android.flash.torchStrengthDefaultLevel}.
      * If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> SINGLE, then the value must be &gt;= 1
-     * and &lt;= android.flash.info.singleStrengthMaxLevel.
+     * and &lt;= {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}.
      * If the application does not set this key and
-     * android.flash.info.singleStrengthMaxLevel &gt; 1,
+     * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} &gt; 1,
      * then the flash will be fired at the default level set by HAL
-     * in android.flash.info.singleStrengthDefaultLevel.
+     * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}.
      * If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of ON_AUTO_FLASH, ON_ALWAYS_FLASH,
      * ON_AUTO_FLASH_REDEYE, ON_EXTERNAL_FLASH values, then the strengthLevel will be ignored.</p>
      * <p><b>Range of valid values:</b><br>
-     * <code>[1-android.flash.info.torchStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+     * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to TORCH;
-     * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+     * <code>[1-{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to SINGLE</p>
      * <p>This key is available on all devices.</p>
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#FLASH_MODE
+     * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL
+     * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+     * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL
+     * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 71066ac..3f9c819 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1276,13 +1276,22 @@
         });
     }
 
+    /**
+     * Whether or not wake requests will be redirected.
+     *
+     * @hide
+     */
+    public boolean getRedirectWake() {
+        return mOverlayConnection != null && mRedirectWake;
+    }
+
     private void wakeUp(boolean fromSystem) {
         if (mDebug) {
             Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking
                     + ", mFinished=" + mFinished);
         }
 
-        if (!fromSystem && mOverlayConnection != null && mRedirectWake) {
+        if (!fromSystem && getRedirectWake()) {
             mOverlayConnection.addConsumer(overlay -> {
                 try {
                     overlay.onWakeRequested();
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index c674968..0dec13f 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,9 +18,10 @@
 
 import static com.android.graphics.hwui.flags.Flags.highContrastTextLuminance;
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
 import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
 
+import android.annotation.ColorInt;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -398,6 +399,20 @@
         mUseBoundsForWidth = useBoundsForWidth;
         mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
         mMinimumFontMetrics = minimumFontMetrics;
+
+        initSpanColors();
+    }
+
+    private void initSpanColors() {
+        if (mSpannedText && Flags.highContrastTextSmallTextRect()) {
+            if (mSpanColors == null) {
+                mSpanColors = new SpanColors();
+            } else {
+                mSpanColors.recycle();
+            }
+        } else {
+            mSpanColors = null;
+        }
     }
 
     /**
@@ -417,6 +432,7 @@
         mSpacingMult = spacingmult;
         mSpacingAdd = spacingadd;
         mSpannedText = text instanceof Spanned;
+        initSpanColors();
     }
 
     /**
@@ -643,20 +659,20 @@
             return null;
         }
 
-        return isHighContrastTextDark() ? BlendMode.MULTIPLY : BlendMode.DIFFERENCE;
+        return isHighContrastTextDark(mPaint.getColor()) ? BlendMode.MULTIPLY
+                : BlendMode.DIFFERENCE;
     }
 
-    private boolean isHighContrastTextDark() {
+    private boolean isHighContrastTextDark(@ColorInt int color) {
         // High-contrast text mode
         // Determine if the text is black-on-white or white-on-black, so we know what blendmode will
         // give the highest contrast and most realistic text color.
         // This equation should match the one in libs/hwui/hwui/DrawTextFunctor.h
         if (highContrastTextLuminance()) {
             var lab = new double[3];
-            ColorUtils.colorToLAB(mPaint.getColor(), lab);
-            return lab[0] < 0.5;
+            ColorUtils.colorToLAB(color, lab);
+            return lab[0] < 50.0;
         } else {
-            var color = mPaint.getColor();
             int channelSum = Color.red(color) + Color.green(color) + Color.blue(color);
             return channelSum < (128 * 3);
         }
@@ -1010,15 +1026,22 @@
         var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
                 mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
 
+        var originalTextColor = mPaint.getColor();
         var bgPaint = mWorkPlainPaint;
         bgPaint.reset();
-        bgPaint.setColor(isHighContrastTextDark() ? Color.WHITE : Color.BLACK);
+        bgPaint.setColor(isHighContrastTextDark(originalTextColor) ? Color.WHITE : Color.BLACK);
         bgPaint.setStyle(Paint.Style.FILL);
 
         int start = getLineStart(firstLine);
         int end = getLineEnd(lastLine);
         // Draw a separate background rectangle for each line of text, that only surrounds the
-        // characters on that line.
+        // characters on that line. But we also have to check the text color for each character, and
+        // make sure we are drawing the correct contrasting background. This is because Spans can
+        // change colors throughout the text and we'll need to match our backgrounds.
+        if (mSpannedText && mSpanColors != null) {
+            mSpanColors.init(mWorkPaint, ((Spanned) mText), start, end);
+        }
+
         forEachCharacterBounds(
                 start,
                 end,
@@ -1028,13 +1051,24 @@
                     int mLastLineNum = -1;
                     final RectF mLineBackground = new RectF();
 
+                    @ColorInt int mLastColor = originalTextColor;
+
                     @Override
                     public void onCharacterBounds(int index, int lineNum, float left, float top,
                             float right, float bottom) {
-                        if (lineNum != mLastLineNum) {
+
+                        var newBackground = determineContrastingBackgroundColor(index);
+                        var hasBgColorChanged = newBackground != bgPaint.getColor();
+
+                        if (lineNum != mLastLineNum || hasBgColorChanged) {
+                            // Draw what we have so far, then reset the rect and update its color
                             drawRect();
                             mLineBackground.set(left, top, right, bottom);
                             mLastLineNum = lineNum;
+
+                            if (hasBgColorChanged) {
+                                bgPaint.setColor(newBackground);
+                            }
                         } else {
                             mLineBackground.union(left, top, right, bottom);
                         }
@@ -1051,8 +1085,36 @@
                             canvas.drawRect(mLineBackground, bgPaint);
                         }
                     }
+
+                    private int determineContrastingBackgroundColor(int index) {
+                        if (!mSpannedText || mSpanColors == null) {
+                            // The text is not Spanned. it's all one color.
+                            return bgPaint.getColor();
+                        }
+
+                        // Sometimes the color will change, but not enough to warrant a background
+                        // color change. e.g. from black to dark grey still gets clamped to black,
+                        // so the background stays white and we don't need to draw a fresh
+                        // background.
+                        var textColor = mSpanColors.getColorAt(index);
+                        if (textColor == SpanColors.NO_COLOR_FOUND) {
+                            textColor = originalTextColor;
+                        }
+                        var hasColorChanged = textColor != mLastColor;
+                        if (hasColorChanged) {
+                            mLastColor = textColor;
+
+                            return isHighContrastTextDark(textColor) ? Color.WHITE : Color.BLACK;
+                        }
+
+                        return bgPaint.getColor();
+                    }
                 }
         );
+
+        if (mSpanColors != null) {
+            mSpanColors.recycle();
+        }
     }
 
     /**
@@ -3580,6 +3642,7 @@
     private float mSpacingAdd;
     private static final Rect sTempRect = new Rect();
     private boolean mSpannedText;
+    @Nullable private SpanColors mSpanColors;
     private TextDirectionHeuristic mTextDir;
     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
     private boolean mIncludePad;
diff --git a/core/java/android/text/SpanColors.java b/core/java/android/text/SpanColors.java
new file mode 100644
index 0000000..fcd242b
--- /dev/null
+++ b/core/java/android/text/SpanColors.java
@@ -0,0 +1,89 @@
+/*
+ * 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.text;
+
+import android.annotation.ColorInt;
+import android.annotation.Nullable;
+import android.graphics.Color;
+import android.text.style.CharacterStyle;
+
+/**
+ * Finds the foreground text color for the given Spanned text so you can iterate through each color
+ * change.
+ *
+ * @hide
+ */
+public class SpanColors {
+    public static final @ColorInt int NO_COLOR_FOUND = Color.TRANSPARENT;
+
+    private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
+            new SpanSet<>(CharacterStyle.class);
+    @Nullable private TextPaint mWorkPaint;
+
+    public SpanColors() {}
+
+    /**
+     * Init for the given text
+     *
+     * @param workPaint A temporary TextPaint object that will be used to calculate the colors. The
+     *                  paint properties will be mutated on calls to {@link #getColorAt(int)} so
+     *                  make sure to reset it before you use it for something else.
+     * @param spanned the text to examine
+     * @param start index to start at
+     * @param end index of the end
+     */
+    public void init(TextPaint workPaint, Spanned spanned, int start, int end) {
+        mWorkPaint = workPaint;
+        mCharacterStyleSpanSet.init(spanned, start, end);
+    }
+
+    /**
+     * Removes all internal references to the spans to avoid memory leaks.
+     */
+    public void recycle() {
+        mWorkPaint = null;
+        mCharacterStyleSpanSet.recycle();
+    }
+
+    /**
+     * Calculates the foreground color of the text at the given character index.
+     *
+     * <p>You must call {@link #init(TextPaint, Spanned, int, int)} before calling this
+     */
+    public @ColorInt int getColorAt(int index) {
+        var finalColor = NO_COLOR_FOUND;
+        // Reset the paint so if we get a CharacterStyle that doesn't actually specify color,
+        // (like UnderlineSpan), we still return no color found.
+        mWorkPaint.setColor(finalColor);
+        for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
+            if ((index >= mCharacterStyleSpanSet.spanStarts[k])
+                    && (index <= mCharacterStyleSpanSet.spanEnds[k])) {
+                final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
+                span.updateDrawState(mWorkPaint);
+
+                finalColor = calculateFinalColor(mWorkPaint);
+            }
+        }
+        return finalColor;
+    }
+
+    private @ColorInt int calculateFinalColor(TextPaint workPaint) {
+        // TODO: can we figure out what the getColorFilter() will do?
+        //  if so, we also need to reset colorFilter before the loop in getColorAt()
+        return workPaint.getColor();
+    }
+}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 4e5cb58..487214c 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -269,22 +269,66 @@
         public Array() {
         }
 
-        public Array(@NonNull Array other) {
-            mControls = other.mControls;
+        /**
+         * @param copyControls whether or not to make a copy of the each {@link InsetsSourceControl}
+         */
+        public Array(@NonNull Array other, boolean copyControls) {
+            setTo(other, copyControls);
         }
 
-        public Array(Parcel in) {
+        public Array(@NonNull Parcel in) {
             readFromParcel(in);
         }
 
-        public void set(@Nullable InsetsSourceControl[] controls) {
-            mControls = controls;
+        /** Updates the current Array to the given Array. */
+        public void setTo(@NonNull Array other, boolean copyControls) {
+            set(other.mControls, copyControls);
         }
 
+        /** Updates the current controls to the given controls. */
+        public void set(@Nullable InsetsSourceControl[] controls, boolean copyControls) {
+            if (controls == null || !copyControls) {
+                mControls = controls;
+                return;
+            }
+            // Make a copy of the array.
+            mControls = new InsetsSourceControl[controls.length];
+            for (int i = mControls.length - 1; i >= 0; i--) {
+                if (controls[i] != null) {
+                    mControls[i] = new InsetsSourceControl(controls[i]);
+                }
+            }
+        }
+
+        /** Gets the controls. */
         public @Nullable InsetsSourceControl[] get() {
             return mControls;
         }
 
+        /** Cleanup {@link SurfaceControl} stored in controls to prevent leak. */
+        public void release() {
+            if (mControls == null) {
+                return;
+            }
+            for (InsetsSourceControl control : mControls) {
+                if (control != null) {
+                    control.release(SurfaceControl::release);
+                }
+            }
+        }
+
+        /** Sets the given flags to all controls. */
+        public void setParcelableFlags(int parcelableFlags) {
+            if (mControls == null) {
+                return;
+            }
+            for (InsetsSourceControl control : mControls) {
+                if (control != null) {
+                    control.setParcelableFlags(parcelableFlags);
+                }
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1cb2765..95c9d7b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23705,12 +23705,6 @@
                     mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                     mPrivateFlags &= ~PFLAG_DIRTY_MASK;
 
-                    // // For VRR to vote the preferred frame rate
-                    if (sToolkitSetFrameRateReadOnlyFlagValue
-                            && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
-                        votePreferredFrameRate();
-                    }
-
                     mPrivateFlags4 |= PFLAG4_HAS_DRAWN;
 
                     // Fast path for layouts with no backgrounds
@@ -23727,6 +23721,12 @@
                         draw(canvas);
                     }
                 }
+
+                // For VRR to vote the preferred frame rate
+                if (sToolkitSetFrameRateReadOnlyFlagValue
+                        && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+                    votePreferredFrameRate();
+                }
             } finally {
                 renderNode.endRecording();
                 setDisplayListProperties(renderNode);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 139285a..496e899 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2276,6 +2276,29 @@
         requestLayout();
     }
 
+    /** Handles messages {@link #MSG_INSETS_CONTROL_CHANGED}. */
+    private void handleInsetsControlChanged(@NonNull InsetsState insetsState,
+            @NonNull InsetsSourceControl.Array activeControls) {
+        final InsetsSourceControl[] controls = activeControls.get();
+
+        if (mTranslator != null) {
+            mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+            mTranslator.translateSourceControlsInScreenToAppWindow(controls);
+        }
+
+        // Deliver state change before control change, such that:
+        // a) When gaining control, controller can compare with server state to evaluate
+        // whether it needs to run animation.
+        // b) When loosing control, controller can restore server state by taking last
+        // dispatched state as truth.
+        mInsetsController.onStateChanged(insetsState);
+        if (mAdded) {
+            mInsetsController.onControlsChanged(controls);
+        } else {
+            activeControls.release();
+        }
+    }
+
     private final DisplayListener mDisplayListener = new DisplayListener() {
         @Override
         public void onDisplayChanged(int displayId) {
@@ -6591,24 +6614,11 @@
                     break;
                 }
                 case MSG_INSETS_CONTROL_CHANGED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-
-                    // Deliver state change before control change, such that:
-                    // a) When gaining control, controller can compare with server state to evaluate
-                    // whether it needs to run animation.
-                    // b) When loosing control, controller can restore server state by taking last
-                    // dispatched state as truth.
-                    mInsetsController.onStateChanged((InsetsState) args.arg1);
-                    InsetsSourceControl[] controls = (InsetsSourceControl[]) args.arg2;
-                    if (mAdded) {
-                        mInsetsController.onControlsChanged(controls);
-                    } else if (controls != null) {
-                        for (InsetsSourceControl control : controls) {
-                            if (control != null) {
-                                control.release(SurfaceControl::release);
-                            }
-                        }
-                    }
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final InsetsState insetsState = (InsetsState) args.arg1;
+                    final InsetsSourceControl.Array activeControls =
+                            (InsetsSourceControl.Array) args.arg2;
+                    handleInsetsControlChanged(insetsState, activeControls);
                     args.recycle();
                     break;
                 }
@@ -9828,25 +9838,9 @@
         mHandler.sendMessage(msg);
     }
 
-    private void dispatchInsetsControlChanged(InsetsState insetsState,
-            InsetsSourceControl[] activeControls) {
-        if (Binder.getCallingPid() == android.os.Process.myPid()) {
-            insetsState = new InsetsState(insetsState, true /* copySource */);
-            if (activeControls != null) {
-                for (int i = activeControls.length - 1; i >= 0; i--) {
-                    activeControls[i] = new InsetsSourceControl(activeControls[i]);
-                }
-            }
-        }
-        if (mTranslator != null) {
-            mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
-            mTranslator.translateSourceControlsInScreenToAppWindow(activeControls);
-        }
-        if (insetsState != null && insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
-            ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged",
-                    getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
-        }
-        SomeArgs args = SomeArgs.obtain();
+    private void dispatchInsetsControlChanged(@NonNull InsetsState insetsState,
+            @NonNull InsetsSourceControl.Array activeControls) {
+        final SomeArgs args = SomeArgs.obtain();
         args.arg1 = insetsState;
         args.arg2 = activeControls;
         mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
@@ -11289,9 +11283,9 @@
                 return;
             }
             // The the parameters from WindowStateResizeItem are already copied.
-            final boolean needCopy =
+            final boolean needsCopy =
                     !isFromResizeItem && (Binder.getCallingPid() == Process.myPid());
-            if (needCopy) {
+            if (needsCopy) {
                 insetsState = new InsetsState(insetsState, true /* copySource */);
                 frames = new ClientWindowFrames(frames);
                 mergedConfiguration = new MergedConfiguration(mergedConfiguration);
@@ -11307,10 +11301,35 @@
             final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem;
             mIsFromTransactionItem = false;
             final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls.get());
+            if (viewAncestor == null) {
+                if (isFromInsetsControlChangeItem) {
+                    activeControls.release();
+                }
+                return;
             }
-            // TODO(b/339380439): no need to post if the call is from InsetsControlChangeItem
+            if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+                ImeTracing.getInstance().triggerClientDump(
+                        "ViewRootImpl#dispatchInsetsControlChanged",
+                        viewAncestor.getInsetsController().getHost().getInputMethodManager(),
+                        null /* icProto */);
+            }
+            // If the UI thread is the same as the current thread that is dispatching
+            // WindowStateInsetsControlChangeItem, then it can run directly.
+            if (isFromInsetsControlChangeItem && viewAncestor.mHandler.getLooper()
+                    == ActivityThread.currentActivityThread().getLooper()) {
+                viewAncestor.handleInsetsControlChanged(insetsState, activeControls);
+                return;
+            }
+            // The parameters from WindowStateInsetsControlChangeItem are already copied.
+            final boolean needsCopy =
+                    !isFromInsetsControlChangeItem && (Binder.getCallingPid() == Process.myPid());
+            if (needsCopy) {
+                insetsState = new InsetsState(insetsState, true /* copySource */);
+                activeControls = new InsetsSourceControl.Array(
+                        activeControls, true /* copyControls */);
+            }
+
+            viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls);
         }
 
         @Override
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 86e5bea..1af9387 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -90,6 +90,19 @@
     public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
             "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
 
+
+    /**
+     * Key used for specifying the version of the view that generated the virtual structure for
+     * itself and its children
+     *
+     * For example, if the virtual structure is generated by a webview of version "104.0.5112.69",
+     * then the value should be "104.0.5112.69"
+     *
+     * @hide
+     */
+    public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
+            "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
+
     /**
      * Set the identifier for this view.
      *
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 12e0814..1fe8180 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -302,6 +302,10 @@
                 }
 
                 @Override
+                public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+                }
+
+                @Override
                 public void onMagnificationChanged(int displayId, @NonNull Region region,
                         MagnificationConfig config) {
                 }
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ab7b226..edf3387 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -169,3 +169,13 @@
     description: "Feature flag for declaring system pinch zoom opt-out apis"
     bug: "315089687"
 }
+
+flag {
+    name: "wait_magnification_system_ui_connection_to_notify_service_connected"
+    namespace: "accessibility"
+    description: "Decide whether AccessibilityService needs to wait until magnification system ui connection is ready to trigger onServiceConnected"
+    bug: "337800504"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 1bd921b..d5398e6 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -56,7 +56,16 @@
     public ClientWindowFrames() {
     }
 
-    public ClientWindowFrames(ClientWindowFrames other) {
+    public ClientWindowFrames(@NonNull ClientWindowFrames other) {
+        setTo(other);
+    }
+
+    private ClientWindowFrames(@NonNull Parcel in) {
+        readFromParcel(in);
+    }
+
+    /** Updates the current frames to the given frames. */
+    public void setTo(@NonNull ClientWindowFrames other) {
         frame.set(other.frame);
         displayFrame.set(other.displayFrame);
         parentFrame.set(other.parentFrame);
@@ -67,10 +76,6 @@
         compatScale = other.compatScale;
     }
 
-    private ClientWindowFrames(Parcel in) {
-        readFromParcel(in);
-    }
-
     /** Needed for AIDL out parameters. */
     public void readFromParcel(Parcel in) {
         frame.readFromParcel(in);
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index f928f50..4c8bad6 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -77,6 +77,14 @@
     private static final String TAG = "SnapshotDrawerUtils";
 
     /**
+     * Used to check if toolkitSetFrameRateReadOnly flag is enabled
+     *
+     * @hide
+     */
+    private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+            android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
+    /**
      * When creating the starting window, we use the exact same layout flags such that we end up
      * with a window with the exact same dimensions etc. However, these flags are not used in layout
      * and might cause other side effects so we exclude them.
@@ -439,6 +447,9 @@
         layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
         layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
         layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
+        if (sToolkitSetFrameRateReadOnlyFlagValue) {
+            layoutParams.setFrameRatePowerSavingsBalanced(false);
+        }
 
         layoutParams.setTitle(title);
         layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 983f46c..5b99ff9 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -73,6 +73,16 @@
 }
 
 flag {
+  name: "immersive_app_repositioning"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Fix immersive apps changing size when repositioning"
+  bug: "334076352"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "camera_compat_for_freeform"
   namespace: "large_screen_experiences_app_compat"
   description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 37b7288..42fa6ac 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -135,7 +135,7 @@
                 new DataSourceParams.Builder()
                         .setBufferExhaustedPolicy(
                                 DataSourceParams
-                                        .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+                                        .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
                         .build();
         mDataSource.register(params);
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java
deleted file mode 100644
index 0104d1f..0000000
--- a/core/java/com/android/internal/util/NewlineNormalizer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.internal.util;
-
-
-import java.util.regex.Pattern;
-
-/**
- * Utility class that replaces consecutive empty lines with single new line.
- * @hide
- */
-public class NewlineNormalizer {
-
-    private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
-
-    // Private constructor to prevent instantiation
-    private NewlineNormalizer() {}
-
-    /**
-     * Replaces consecutive newlines with a single newline in the input text.
-     */
-    public static String normalizeNewlines(String text) {
-        return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
-    }
-}
diff --git a/core/java/com/android/internal/util/NotificationBigTextNormalizer.java b/core/java/com/android/internal/util/NotificationBigTextNormalizer.java
new file mode 100644
index 0000000..80d4095
--- /dev/null
+++ b/core/java/com/android/internal/util/NotificationBigTextNormalizer.java
@@ -0,0 +1,123 @@
+/*
+ * 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.internal.util;
+
+
+import android.annotation.NonNull;
+import android.os.Trace;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility class that normalizes BigText style Notification content.
+ * @hide
+ */
+public class NotificationBigTextNormalizer {
+
+    private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
+    private static final Pattern HORIZONTAL_WHITESPACES = Pattern.compile("\\h+");
+
+    // Private constructor to prevent instantiation
+    private NotificationBigTextNormalizer() {}
+
+    /**
+     * Normalizes the given text by collapsing consecutive new lines into single one and cleaning
+     * up each line by removing zero-width characters, invisible formatting characters, and
+     * collapsing consecutive whitespace into single space.
+     */
+    @NonNull
+    public static String normalizeBigText(@NonNull String text) {
+        try {
+            Trace.beginSection("NotifBigTextNormalizer#normalizeBigText");
+            text = MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
+            text = HORIZONTAL_WHITESPACES.matcher(text).replaceAll(" ");
+            text = normalizeLines(text);
+            return text;
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    /**
+     * Normalizes lines in a text by removing zero-width characters, invisible formatting
+     * characters, and collapsing consecutive whitespace into single space.
+     *
+     * <p>
+     * This method processes the input text line by line. It eliminates zero-width
+     * characters (U+200B to U+200D, U+FEFF, U+034F), invisible formatting
+     * characters (U+2060 to U+2065, U+206A to U+206F, U+FFF9 to U+FFFB),
+     * and replaces any sequence of consecutive whitespace characters with a single space.
+     * </p>
+     *
+     * <p>
+     * Additionally, the method trims trailing whitespace from each line and removes any
+     * resulting empty lines.
+     * </p>
+     */
+    @NonNull
+    private static String normalizeLines(@NonNull String text) {
+        String[] lines = text.split("\n");
+        final StringBuilder textSB = new StringBuilder(text.length());
+        for (int i = 0; i < lines.length; i++) {
+            final String line = lines[i];
+            final StringBuilder lineSB = new StringBuilder(line.length());
+            boolean spaceSeen = false;
+            for (int j = 0; j < line.length(); j++) {
+                final char character = line.charAt(j);
+
+                // Skip ZERO WIDTH characters
+                if ((character >= '\u200B' && character <= '\u200D')
+                        || character == '\uFEFF' || character == '\u034F') {
+                    continue;
+                }
+                // Skip INVISIBLE_FORMATTING_CHARACTERS
+                if ((character >= '\u2060' && character <= '\u2065')
+                        || (character >= '\u206A' && character <= '\u206F')
+                        || (character >= '\uFFF9' && character <= '\uFFFB')) {
+                    continue;
+                }
+
+                if (isSpace(character)) {
+                    // eliminate consecutive spaces....
+                    if (!spaceSeen) {
+                        lineSB.append(" ");
+                    }
+                    spaceSeen = true;
+                } else {
+                    spaceSeen = false;
+                    lineSB.append(character);
+                }
+            }
+            // trim line.
+            final String currentLine = lineSB.toString().trim();
+
+            // don't add empty lines after trim.
+            if (currentLine.length() > 0) {
+                if (textSB.length() > 0) {
+                    textSB.append("\n");
+                }
+                textSB.append(currentLine);
+            }
+        }
+
+        return textSB.toString();
+    }
+
+    private static boolean isSpace(char ch) {
+        return ch != '\n' && Character.isSpaceChar(ch);
+    }
+}
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index 0f4615a..58bddae 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -59,7 +59,7 @@
     @Override
     protected void onFinishInflate() {
         // If showing the app icon, we don't need background or padding.
-        if (Flags.notificationsUseAppIcon()) {
+        if (Flags.notificationsUseAppIcon() || Flags.notificationsUseAppIconInRow()) {
             setPadding(0, 0, 0, 0);
             setBackground(null);
         }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index d48cdc4..eaff760 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -713,6 +713,19 @@
             AudioSystem::getForceUse(static_cast<audio_policy_force_use_t>(usage)));
 }
 
+static jint android_media_AudioSystem_setDeviceAbsoluteVolumeEnabled(JNIEnv *env, jobject thiz,
+                                                                     jint device, jstring address,
+                                                                     jboolean enabled,
+                                                                     jint stream) {
+    const char *c_address = env->GetStringUTFChars(address, nullptr);
+    int state = check_AudioSystem_Command(
+            AudioSystem::setDeviceAbsoluteVolumeEnabled(static_cast<audio_devices_t>(device),
+                                                        c_address, enabled,
+                                                        static_cast<audio_stream_type_t>(stream)));
+    env->ReleaseStringUTFChars(address, c_address);
+    return state;
+}
+
 static jint
 android_media_AudioSystem_initStreamVolume(JNIEnv *env, jobject thiz, jint stream, jint indexMin, jint indexMax)
 {
@@ -3373,6 +3386,7 @@
          MAKE_AUDIO_SYSTEM_METHOD(setPhoneState),
          MAKE_AUDIO_SYSTEM_METHOD(setForceUse),
          MAKE_AUDIO_SYSTEM_METHOD(getForceUse),
+         MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled),
          MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume),
          MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex),
          MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex),
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 31f4e64..d426f12 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -88,7 +88,7 @@
         ALOGD("Total number of LOAD segments %zu", programHeaders.size());
 
         ALOGD("Size before punching holes st_blocks: %" PRIu64
-              ", st_blksize: %ld, st_size: %" PRIu64 "",
+              ", st_blksize: %d, st_size: %" PRIu64 "",
               beforePunch.st_blocks, beforePunch.st_blksize,
               static_cast<uint64_t>(beforePunch.st_size));
     }
@@ -193,7 +193,7 @@
             ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
             return false;
         }
-        ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
+        ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %d, st_size: %" PRIu64
               "",
               afterPunch.st_blocks, afterPunch.st_blksize,
               static_cast<uint64_t>(afterPunch.st_size));
@@ -271,7 +271,7 @@
     uint64_t blockSize = beforePunch.st_blksize;
     IF_ALOGD() {
         ALOGD("Extra field length: %hu,  Size before punching holes st_blocks: %" PRIu64
-              ", st_blksize: %ld, st_size: %" PRIu64 "",
+              ", st_blksize: %d, st_size: %" PRIu64 "",
               extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
               static_cast<uint64_t>(beforePunch.st_size));
     }
@@ -346,7 +346,7 @@
             return false;
         }
         ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
-              ", st_blksize: %ld, st_size: %" PRIu64 "",
+              ", st_blksize: %d, st_size: %" PRIu64 "",
               afterPunch.st_blocks, afterPunch.st_blksize,
               static_cast<uint64_t>(afterPunch.st_size));
     }
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index 3abc462..e560a94 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -20,7 +20,7 @@
 package android.app;
 
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
 
 /**
  * An android.app.ApplicationExitInfo object.
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index d9ed911..c137533 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -20,7 +20,7 @@
 package android.app;
 
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
 
 /**
  * An android.app.ApplicationStartInfo object.
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 4c84944..97f8148 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -21,7 +21,7 @@
 
 import "frameworks/base/core/proto/android/os/powermanager.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/job/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/job/job_enums.proto";
 import "frameworks/proto_logging/stats/enums/telephony/enums.proto";
 
 message BatteryStatsProto {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index e3a438d..921c41c 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -35,7 +35,7 @@
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/util/common.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
 
 option java_multiple_files = true;
 
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 00127c1..a1e3dc1 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -31,7 +31,7 @@
 import "frameworks/base/core/proto/android/server/statlogger.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
 import "frameworks/base/core/proto/android/util/quotatracker.proto";
-import "frameworks/proto_logging/stats/enums/app/job/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/job/job_enums.proto";
 import "frameworks/proto_logging/stats/enums/server/job/enums.proto";
 
 message JobSchedulerServiceDumpProto {
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 2f865af..593bbc6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -26,7 +26,7 @@
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
 import "frameworks/proto_logging/stats/enums/os/enums.proto";
 import "frameworks/proto_logging/stats/enums/view/enums.proto";
 
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 89c2b3c..b972882 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,7 +128,8 @@
                 0x222222,                // colorBackground
                 0x333333,                // statusBarColor
                 0x444444,                // navigationBarColor
-                0,                       // statusBarAppearance
+                0x555555,                // systemBarsAppeareance
+                0x666666,                // topOpaqueSystemBarsAppeareance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -153,7 +154,8 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
-                0,                         // statusBarAppearance
+                0x555555,                  // systemBarsAppeareance
+                0x666666,                  // topOpaqueSystemBarsAppeareance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -169,7 +171,8 @@
                 0x2222222,               // colorBackground
                 0x3333332,               // statusBarColor
                 0x4444442,               // navigationBarColor
-                0,                       // statusBarAppearance
+                0x5555552,               // systemBarsAppeareance
+                0x6666662,               // topOpaqueSystemBarsAppeareance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -200,7 +203,8 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
-                0,                         // statusBarAppearance
+                0x555555,                  // systemBarsAppeareance
+                0x666666,                  // topOpaqueSystemBarsAppeareance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -223,7 +227,8 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
-                0,                         // statusBarAppearance
+                0x555555,                  // systemBarsAppeareance
+                0x666666,                  // topOpaqueSystemBarsAppeareance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -256,6 +261,8 @@
             assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
             assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
             assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance());
+            assertEquals(td1.getTopOpaqueSystemBarsAppearance(),
+                    td2.getTopOpaqueSystemBarsAppearance());
             assertEquals(td1.getResizeMode(), td2.getResizeMode());
             assertEquals(td1.getMinWidth(), td2.getMinWidth());
             assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 3735274..c7060ad 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
@@ -39,7 +41,6 @@
 import android.view.InsetsState;
 import android.window.ActivityWindowInfo;
 import android.window.ClientWindowFrames;
-import android.window.WindowContext;
 import android.window.WindowContextInfo;
 
 import androidx.test.filters.SmallTest;
@@ -73,8 +74,6 @@
     @Mock
     private IBinder mWindowClientToken;
     @Mock
-    private WindowContext mWindowContext;
-    @Mock
     private IWindow mWindow;
 
     // Can't mock final class.
@@ -176,4 +175,17 @@
 
         verify(mWindow).insetsControlChanged(mInsetsState, mActiveControls);
     }
+
+    @Test
+    public void testWindowStateInsetsControlChangeItem_executeError() throws RemoteException {
+        doThrow(new RemoteException()).when(mWindow).insetsControlChanged(any(), any());
+
+        mActiveControls = spy(mActiveControls);
+        final WindowStateInsetsControlChangeItem item = WindowStateInsetsControlChangeItem.obtain(
+                mWindow, mInsetsState, mActiveControls);
+        item.mActiveControls = mActiveControls;
+        item.execute(mHandler, mPendingActions);
+
+        verify(mActiveControls).release();
+    }
 }
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 1c12362..98f8b7f 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -39,6 +39,7 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.text.Layout.Alignment;
+import android.text.style.ForegroundColorSpan;
 import android.text.style.StrikethroughSpan;
 
 import androidx.test.filters.SmallTest;
@@ -933,6 +934,83 @@
         expect.that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
     }
 
+    @Test
+    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+    public void highContrastTextEnabled_testDrawMulticolorText_drawsBlackAndWhiteBackgrounds() {
+        /*
+        Here's what the final render should look like:
+
+       Text  |   Background
+     ========================
+        al   |    BW
+        w    |    WW
+        ei   |    WW
+        \t;  |    WW
+        s    |    BB
+        df   |    BB
+        s    |    BB
+        df   |    BB
+        @    |    BB
+      ------------------------
+         */
+
+        mTextPaint.setColor(Color.WHITE);
+
+        mSpannedText.setSpan(
+                // Can't use DKGREY because it is right on the cusp of clamping white
+                new ForegroundColorSpan(0xFF332211),
+                /* start= */ 1,
+                /* end= */ 6,
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+        );
+        mSpannedText.setSpan(
+                new ForegroundColorSpan(Color.LTGRAY),
+                /* start= */ 8,
+                /* end= */ 11,
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+        );
+        Layout layout = new StaticLayout(mSpannedText, mTextPaint, mWidth,
+                mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+        final int width = 256;
+        final int height = 256;
+        MockCanvas c = new MockCanvas(width, height);
+        c.setHighContrastTextEnabled(true);
+        layout.draw(
+                c,
+                /* highlightPaths= */ null,
+                /* highlightPaints= */ null,
+                /* selectionPath= */ null,
+                /* selectionPaint= */ null,
+                /* cursorOffsetVertical= */ 0
+        );
+        List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+        var highlightsDrawn = 0;
+        var numColorChangesWithinOneLine = 1;
+        var textsDrawn = STATIC_LINE_COUNT + numColorChangesWithinOneLine;
+        var backgroundRectsDrawn = STATIC_LINE_COUNT + numColorChangesWithinOneLine;
+        expect.withMessage("wrong number of drawCommands: " + drawCommands)
+                .that(drawCommands.size())
+                .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
+
+        var backgroundCommands = drawCommands.stream()
+                .filter(it -> it.rect != null)
+                .toList();
+
+        expect.that(backgroundCommands.get(0).paint.getColor()).isEqualTo(Color.BLACK);
+        expect.that(backgroundCommands.get(1).paint.getColor()).isEqualTo(Color.WHITE);
+        expect.that(backgroundCommands.get(2).paint.getColor()).isEqualTo(Color.WHITE);
+        expect.that(backgroundCommands.get(3).paint.getColor()).isEqualTo(Color.WHITE);
+        expect.that(backgroundCommands.get(4).paint.getColor()).isEqualTo(Color.WHITE);
+        expect.that(backgroundCommands.get(5).paint.getColor()).isEqualTo(Color.BLACK);
+        expect.that(backgroundCommands.get(6).paint.getColor()).isEqualTo(Color.BLACK);
+        expect.that(backgroundCommands.get(7).paint.getColor()).isEqualTo(Color.BLACK);
+        expect.that(backgroundCommands.get(8).paint.getColor()).isEqualTo(Color.BLACK);
+        expect.that(backgroundCommands.get(9).paint.getColor()).isEqualTo(Color.BLACK);
+
+        expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn);
+    }
+
     private static final class MockCanvas extends Canvas {
 
         static class DrawCommand {
diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java
new file mode 100644
index 0000000..3d8d8f9
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpanColorsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.text;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.platform.test.annotations.Presubmit;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.text.style.UnderlineSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SpanColorsTest {
+    private final TextPaint mWorkPaint = new TextPaint();
+    private SpanColors mSpanColors;
+    private SpannableString mSpannedText;
+
+    @Before
+    public void setup() {
+        mSpanColors = new SpanColors();
+        mSpannedText = new SpannableString("Hello world! This is a test.");
+        mSpannedText.setSpan(new ForegroundColorSpan(Color.RED), 0, 4,
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        mSpannedText.setSpan(new ForegroundColorSpan(Color.GREEN), 6, 11,
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        mSpannedText.setSpan(new UnderlineSpan(), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        mSpannedText.setSpan(new ImageSpan(new ShapeDrawable()), 1, 2,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannedText.setSpan(new ForegroundColorSpan(Color.BLUE), 12, 16,
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+    }
+
+    @Test
+    public void testNoColorFound() {
+        mSpanColors.init(mWorkPaint, mSpannedText, 25, 30); // Beyond the spans
+        assertThat(mSpanColors.getColorAt(27)).isEqualTo(SpanColors.NO_COLOR_FOUND);
+    }
+
+    @Test
+    public void testSingleColorSpan() {
+        mSpanColors.init(mWorkPaint, mSpannedText, 1, 4);
+        assertThat(mSpanColors.getColorAt(3)).isEqualTo(Color.RED);
+    }
+
+    @Test
+    public void testMultipleColorSpans() {
+        mSpanColors.init(mWorkPaint, mSpannedText, 0, mSpannedText.length());
+        assertThat(mSpanColors.getColorAt(2)).isEqualTo(Color.RED);
+        assertThat(mSpanColors.getColorAt(5)).isEqualTo(SpanColors.NO_COLOR_FOUND);
+        assertThat(mSpanColors.getColorAt(8)).isEqualTo(Color.GREEN);
+        assertThat(mSpanColors.getColorAt(13)).isEqualTo(Color.BLUE);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index a7f8176..94e187a 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1287,6 +1287,31 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
+    public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable {
+        mView = new View(sContext);
+        double delta = 0.1;
+        float pixelsPerSecond = 1000_000;
+        float expectedFrameRate = 120;
+        attachViewToWindow(mView);
+        sInstrumentation.waitForIdleSync();
+        ViewRootImpl viewRoot = mView.getViewRootImpl();
+        waitForFrameRateCategoryToSettle(mView);
+
+        sInstrumentation.runOnMainSync(() -> {
+            mView.setFrameContentVelocity(pixelsPerSecond);
+            mView.invalidate();
+            assertEquals(0, viewRoot.getPreferredFrameRate(), delta);
+            assertEquals(0, viewRoot.getLastPreferredFrameRate(), delta);
+            runAfterDraw(() -> {
+                assertEquals(expectedFrameRate, viewRoot.getPreferredFrameRate(), delta);
+                assertEquals(expectedFrameRate, viewRoot.getLastPreferredFrameRate(), delta);
+            });
+        });
+        waitForAfterDraw();
+    }
+
+    @Test
     public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
         mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
         ShellIdentityUtils.invokeWithShellPermissions(() -> {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index b5c264c..5a6824b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -146,6 +146,10 @@
 
     public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {}
 
+    public boolean isMagnificationSystemUIConnected() {
+        return false;
+    }
+
     public boolean setSoftKeyboardShowMode(int showMode) {
         return false;
     }
diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
deleted file mode 100644
index bcdac61..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.internal.util;
-
-import static junit.framework.Assert.assertEquals;
-
-
-import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test for {@link NewlineNormalizer}
- * @hide
- */
-@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
-@RunWith(AndroidJUnit4.class)
-public class NewlineNormalizerTest {
-
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Test
-    public void testEmptyInput() {
-        assertEquals("", NewlineNormalizer.normalizeNewlines(""));
-    }
-
-    @Test
-    public void testSingleNewline() {
-        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
-    }
-
-    @Test
-    public void testMultipleConsecutiveNewlines() {
-        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
-    }
-
-    @Test
-    public void testNewlinesWithSpacesAndTabs() {
-        String input = "Line 1\n  \n \t \n\tLine 2";
-        // Adjusted expected output to include the tab character
-        String expected = "Line 1\n\tLine 2";
-        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
-    }
-
-    @Test
-    public void testMixedNewlineCharacters() {
-        String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
-        String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
-        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
-    }
-}
diff --git a/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java
new file mode 100644
index 0000000..1f2e24a
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.internal.util;
+
+import static junit.framework.Assert.assertEquals;
+
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link NotificationBigTextNormalizer}
+ * @hide
+ */
+@DisabledOnRavenwood(blockedBy = NotificationBigTextNormalizer.class)
+@RunWith(AndroidJUnit4.class)
+public class NotificationBigTextNormalizerTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+
+    @Test
+    public void testEmptyInput() {
+        assertEquals("", NotificationBigTextNormalizer.normalizeBigText(""));
+    }
+
+    @Test
+    public void testSingleNewline() {
+        assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n"));
+    }
+
+    @Test
+    public void testMultipleConsecutiveNewlines() {
+        assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n\n\n\n\n"));
+    }
+
+    @Test
+    public void testNewlinesWithSpacesAndTabs() {
+        String input = "Line 1\n  \n \t \n\tLine 2";
+        // Adjusted expected output to include the tab character
+        String expected = "Line 1\nLine 2";
+        assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+    }
+
+    @Test
+    public void testMixedNewlineCharacters() {
+        String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
+        String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
+        assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+    }
+
+    @Test
+    public void testConsecutiveSpaces() {
+        // Only spaces
+        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+                + "              is   a                         test."));
+        // Zero width characters bw spaces.
+        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+                + "\u200B \u200B \u200B \u200B \u200B \u200B \u200B \u200Bis\uFEFF \uFEFF \uFEFF"
+                + " \uFEFFa \u034F \u034F \u034F \u034F \u034F \u034Ftest."));
+
+        // Invisible formatting characters bw spaces.
+        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+                + "\u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061is\u206E \u206E \u206E"
+                + " \u206Ea \uFFFB \uFFFB \uFFFB \uFFFB \uFFFB \uFFFBtest."));
+        // Non breakable spaces
+        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+                + "\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0is\u2005 \u2005 \u2005"
+                + " \u2005a\u2005\u2005\u2005 \u2005\u2005\u2005test."));
+    }
+
+    @Test
+    public void testZeroWidthCharRemoval() {
+        // Test each character individually
+        char[] zeroWidthChars = { '\u200B', '\u200C', '\u200D', '\uFEFF', '\u034F' };
+
+        for (char c : zeroWidthChars) {
+            String input = "Test" + c + "string";
+            String expected = "Teststring";
+            assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+        }
+    }
+
+    @Test
+    public void testWhitespaceReplacement() {
+        assertEquals("This text has horizontal whitespace.",
+                NotificationBigTextNormalizer.normalizeBigText(
+                        "This\ttext\thas\thorizontal\twhitespace."));
+        assertEquals("This text has mixed whitespace.",
+                NotificationBigTextNormalizer.normalizeBigText(
+                        "This  text  has \u00A0 mixed\u2009whitespace."));
+        assertEquals("This text has leading and trailing whitespace.",
+                NotificationBigTextNormalizer.normalizeBigText(
+                        "\t This text has leading and trailing whitespace. \n"));
+    }
+
+    @Test
+    public void testInvisibleFormattingCharacterRemoval() {
+        // Test each character individually
+        char[] invisibleFormattingChars = {
+                '\u2060', '\u2061', '\u2062', '\u2063', '\u2064', '\u2065',
+                '\u206A', '\u206B', '\u206C', '\u206D', '\u206E', '\u206F',
+                '\uFFF9', '\uFFFA', '\uFFFB'
+        };
+
+        for (char c : invisibleFormattingChars) {
+            String input = "Test " + c + "string";
+            String expected = "Test string";
+            assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+        }
+    }
+    @Test
+    public void testNonBreakSpaceReplacement() {
+        // Test each character individually
+        char[] nonBreakSpaces = {
+                '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002',
+                '\u2003', '\u2004', '\u2005', '\u2006', '\u2007',
+                '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000'
+        };
+
+        for (char c : nonBreakSpaces) {
+            String input = "Test" + c + "string";
+            String expected = "Test string";
+            assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+        }
+    }
+}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index d359a90..3cff915 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -1149,6 +1149,8 @@
 
         /**
          * Sets the serial number used for the certificate of the generated key pair.
+         * To ensure compatibility with devices and certificate parsers, the value
+         * should be 20 bytes or shorter (see RFC 5280 section 4.1.2.2).
          *
          * <p>By default, the serial number is {@code 1}.
          */
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
deleted file mode 100644
index 0ac6265..0000000
--- a/ktfmt_includes.txt
+++ /dev/null
@@ -1,740 +0,0 @@
-+services/permission
-+packages/SystemUI
--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
--packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
--packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
--packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
--packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
--packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
--packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
--packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
--packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
--packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
--packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
--packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
--packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt
--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
--packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
--packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
--packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt
--packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt
--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
--packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
--packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt
--packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
--packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
--packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
--packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
--packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
--packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
--packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
--packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
--packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
--packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
--packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
--packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt
--packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
--packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
--packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
--packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt
--packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt
--packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
--packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
--packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
--packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
--packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
--packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
--packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
--packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
--packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
--packages/SystemUI/src/com/android/systemui/controls/controller/StatefulControlSubscriber.kt
--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt
--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
--packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
--packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt
--packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
--packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt
--packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
--packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
--packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
--packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt
--packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
--packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
--packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
--packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
--packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt
--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
--packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
--packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt
--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
--packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
--packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
--packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
--packages/SystemUI/src/com/android/systemui/dump/LogBufferFreezer.kt
--packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
--packages/SystemUI/src/com/android/systemui/flags/Flags.kt
--packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
--packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
--packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
--packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
--packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
--packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
--packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
--packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
--packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
--packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
--packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt
--packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
--packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
--packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
--packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
--packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
--packages/SystemUI/src/com/android/systemui/qs/QSExpansionPathInterpolator.kt
--packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
--packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
--packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
--packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
--packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt
--packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
--packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt
--packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
--packages/SystemUI/src/com/android/systemui/qs/external/QSExternalModule.kt
--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt
--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
--packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
--packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
--packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
--packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
--packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
--packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
--packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
--packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt
--packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
--packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt
--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
--packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
--packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
--packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
--packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
--packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
--packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
--packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
--packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
--packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt
--packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt
--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
--packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt
--packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
--packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
--packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt
--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
--packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
--packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
--packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
--packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt
--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/FeedbackIcon.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/RemoteInputViewModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarIconBlocklist.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyState.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletController.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
--packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
--packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
--packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt
--packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
--packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
--packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
--packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt
--packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt
--packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
--packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
--packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
--packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
--packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt
--packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt
--packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
--packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt
--packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
--packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt
--packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt
--packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
--packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
--packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt
--packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
--packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
--packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
--packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
--packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt
--packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt
--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
--packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
--packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt
--packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
--packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
--packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
--packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
--packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt
--packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
--packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt
--packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
--packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
--packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
--packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
--packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt
--packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt
--packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
--packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
--packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
--packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
--packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
--packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
--packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
--packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
--packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
--packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
--packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
--packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt
--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
--packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
--packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
--packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
--packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
--packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
--packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 6b95711..774b212 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -200,6 +200,10 @@
             }
 
             // At this point, a divider is required.
+            final TaskFragmentContainer primaryContainer =
+                    topSplitContainer.getPrimaryContainer();
+            final TaskFragmentContainer secondaryContainer =
+                    topSplitContainer.getSecondaryContainer();
 
             // Create the decor surface if one is not available yet.
             final SurfaceControl decorSurface = parentInfo.getDecorSurface();
@@ -207,41 +211,44 @@
                 // Clean up when the decor surface is currently unavailable.
                 removeDivider();
                 // Request to create the decor surface
-                createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer());
+                createOrMoveDecorSurfaceLocked(wct, primaryContainer);
                 return;
             }
 
             // Update the decor surface owner if needed.
             boolean isDraggableExpandType =
                     SplitAttributesHelper.isDraggableExpandType(splitAttributes);
-            final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType
-                    ? topSplitContainer.getSecondaryContainer()
-                    : topSplitContainer.getPrimaryContainer();
+            final TaskFragmentContainer decorSurfaceOwnerContainer =
+                    isDraggableExpandType ? secondaryContainer : primaryContainer;
 
             if (!Objects.equals(
                     mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) {
                 createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer);
             }
-            final boolean isVerticalSplit = isVerticalSplit(topSplitContainer);
-            final boolean isReversedLayout = isReversedLayout(
-                    topSplitContainer.getCurrentSplitAttributes(),
-                    parentInfo.getConfiguration());
+
+            final Configuration parentConfiguration = parentInfo.getConfiguration();
+            final Rect taskBounds = parentConfiguration.windowConfiguration.getBounds();
+            final boolean isVerticalSplit = isVerticalSplit(splitAttributes);
+            final boolean isReversedLayout = isReversedLayout(splitAttributes, parentConfiguration);
+            final int dividerWidthPx = getDividerWidthPx(dividerAttributes);
 
             updateProperties(
                     new Properties(
-                            parentInfo.getConfiguration(),
+                            parentConfiguration,
                             dividerAttributes,
                             decorSurface,
                             getInitialDividerPosition(
-                                    topSplitContainer, isVerticalSplit, isReversedLayout),
+                                    primaryContainer, secondaryContainer, taskBounds,
+                                    dividerWidthPx, isDraggableExpandType, isVerticalSplit,
+                                    isReversedLayout),
                             isVerticalSplit,
                             isReversedLayout,
                             parentInfo.getDisplayId(),
                             isDraggableExpandType,
-                            getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(),
-                                    DEFAULT_PRIMARY_VEIL_COLOR),
-                            getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(),
-                                    DEFAULT_SECONDARY_VEIL_COLOR)
+                            getContainerBackgroundColor(
+                                    primaryContainer, DEFAULT_PRIMARY_VEIL_COLOR),
+                            getContainerBackgroundColor(
+                                    secondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR)
                     ));
         }
     }
@@ -338,32 +345,31 @@
 
     @VisibleForTesting
     static int getInitialDividerPosition(
-            @NonNull SplitContainer splitContainer,
+            @NonNull TaskFragmentContainer primaryContainer,
+            @NonNull TaskFragmentContainer secondaryContainer,
+            @NonNull Rect taskBounds,
+            int dividerWidthPx,
+            boolean isDraggableExpandType,
             boolean isVerticalSplit,
             boolean isReversedLayout) {
-        final Rect primaryBounds =
-                splitContainer.getPrimaryContainer().getLastRequestedBounds();
-        final Rect secondaryBounds =
-                splitContainer.getSecondaryContainer().getLastRequestedBounds();
-        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
-
-        if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) {
-            // If the container is fully expanded by dragging the divider, we display the divider
-            // on the edge.
-            final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes());
+        if (isDraggableExpandType) {
+            // If the secondary container is fully expanded by dragging the divider, we display the
+            // divider on the edge.
             final int fullyExpandedPosition = isVerticalSplit
-                    ? primaryBounds.right - dividerWidth
-                    : primaryBounds.bottom - dividerWidth;
+                    ? taskBounds.width() - dividerWidthPx
+                    : taskBounds.height() - dividerWidthPx;
             return isReversedLayout ? fullyExpandedPosition : 0;
         } else {
+            final Rect primaryBounds = primaryContainer.getLastRequestedBounds();
+            final Rect secondaryBounds = secondaryContainer.getLastRequestedBounds();
             return isVerticalSplit
                     ? Math.min(primaryBounds.right, secondaryBounds.right)
                     : Math.min(primaryBounds.bottom, secondaryBounds.bottom);
         }
     }
 
-    private static boolean isVerticalSplit(@NonNull SplitContainer splitContainer) {
-        final int layoutDirection = splitContainer.getCurrentSplitAttributes().getLayoutDirection();
+    private static boolean isVerticalSplit(@NonNull SplitAttributes splitAttributes) {
+        final int layoutDirection = splitAttributes.getLayoutDirection();
         switch (layoutDirection) {
             case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
             case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
@@ -510,7 +516,7 @@
             if (mProperties != null && mRenderer != null) {
                 final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
                 mDividerPosition = calculateDividerPosition(
-                        event, taskBounds, mRenderer.mDividerWidthPx,
+                        event, taskBounds, mProperties.mDividerWidthPx,
                         mProperties.mDividerAttributes, mProperties.mIsVerticalSplit,
                         calculateMinPosition(), calculateMaxPosition());
                 mRenderer.setDividerPosition(mDividerPosition);
@@ -676,8 +682,8 @@
         final int minPosition = calculateMinPosition();
         final int maxPosition = calculateMaxPosition();
         final int fullyExpandedPosition = mProperties.mIsVerticalSplit
-                ? taskBounds.right - mRenderer.mDividerWidthPx
-                : taskBounds.bottom - mRenderer.mDividerWidthPx;
+                ? taskBounds.width() - mProperties.mDividerWidthPx
+                : taskBounds.height() - mProperties.mDividerWidthPx;
 
         if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
             final float displayDensity = getDisplayDensity();
@@ -782,7 +788,7 @@
     private int calculateMinPosition() {
         return calculateMinPosition(
                 mProperties.mConfiguration.windowConfiguration.getBounds(),
-                mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+                mProperties.mDividerWidthPx, mProperties.mDividerAttributes,
                 mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
     }
 
@@ -790,7 +796,7 @@
     private int calculateMaxPosition() {
         return calculateMaxPosition(
                 mProperties.mConfiguration.windowConfiguration.getBounds(),
-                mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+                mProperties.mDividerWidthPx, mProperties.mDividerAttributes,
                 mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
     }
 
@@ -828,13 +834,12 @@
      * Returns the new split ratio of the {@link SplitContainer} based on the current divider
      * position.
      */
-    float calculateNewSplitRatio(@NonNull SplitContainer topSplitContainer) {
+    float calculateNewSplitRatio() {
         synchronized (mLock) {
             return calculateNewSplitRatio(
-                    topSplitContainer,
                     mDividerPosition,
                     mProperties.mConfiguration.windowConfiguration.getBounds(),
-                    mRenderer.mDividerWidthPx,
+                    mProperties.mDividerWidthPx,
                     mProperties.mIsVerticalSplit,
                     mProperties.mIsReversedLayout,
                     calculateMinPosition(),
@@ -846,21 +851,20 @@
     private static boolean isDraggingToFullscreenAllowed(
             @NonNull DividerAttributes dividerAttributes) {
         // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
-        // updated.
-        return true;
+        // updated to v7.
+        return false;
     }
 
     /**
      * Returns the new split ratio of the {@link SplitContainer} based on the current divider
      * position.
      *
-     * @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio.
      * @param dividerPosition the divider position. See {@link #mDividerPosition}.
      * @param taskBounds the task bounds
      * @param dividerWidthPx the width of the divider in pixels.
      * @param isVerticalSplit if {@code true}, the split is a vertical split. If {@code false}, the
      *                        split is a horizontal split. See
-     *                        {@link #isVerticalSplit(SplitContainer)}.
+     *                        {@link #isVerticalSplit(SplitAttributes)}.
      * @param isReversedLayout if {@code true}, the split layout is reversed, i.e. right-to-left or
      *                         bottom-to-top. If {@code false}, the split is not reversed, i.e.
      *                         left-to-right or top-to-bottom. See
@@ -871,7 +875,6 @@
      */
     @VisibleForTesting
     static float calculateNewSplitRatio(
-            @NonNull SplitContainer topSplitContainer,
             int dividerPosition,
             @NonNull Rect taskBounds,
             int dividerWidthPx,
@@ -896,8 +899,6 @@
             dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
         }
 
-        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
-        final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
         final int usableSize = isVerticalSplit
                 ? taskBounds.width() - dividerWidthPx
                 : taskBounds.height() - dividerWidthPx;
@@ -905,13 +906,13 @@
         final float newRatio;
         if (isVerticalSplit) {
             final int newPrimaryWidth = isReversedLayout
-                    ? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx))
-                    : (dividerPosition - origPrimaryBounds.left);
+                    ? taskBounds.width() - (dividerPosition + dividerWidthPx)
+                    : dividerPosition;
             newRatio = 1.0f * newPrimaryWidth / usableSize;
         } else {
             final int newPrimaryHeight = isReversedLayout
-                    ? (origPrimaryBounds.bottom - (dividerPosition + dividerWidthPx))
-                    : (dividerPosition - origPrimaryBounds.top);
+                    ? taskBounds.height() - (dividerPosition + dividerWidthPx)
+                    : dividerPosition;
             newRatio = 1.0f * newPrimaryHeight / usableSize;
         }
         return newRatio;
@@ -966,6 +967,7 @@
         private final boolean mIsDraggableExpandType;
         private final Color mPrimaryVeilColor;
         private final Color mSecondaryVeilColor;
+        private final int mDividerWidthPx;
 
         @VisibleForTesting
         Properties(
@@ -989,6 +991,7 @@
             mIsDraggableExpandType = isDraggableExpandType;
             mPrimaryVeilColor = primaryVeilColor;
             mSecondaryVeilColor = secondaryVeilColor;
+            mDividerWidthPx = getDividerWidthPx(dividerAttributes);
         }
 
         /**
@@ -1055,7 +1058,6 @@
         private final View.OnTouchListener mListener;
         @NonNull
         private Properties mProperties;
-        private int mDividerWidthPx;
         private int mHandleWidthPx;
         @Nullable
         private SurfaceControl mPrimaryVeil;
@@ -1095,7 +1097,6 @@
         /** Updates the divider when initializing or when properties are changed */
         @VisibleForTesting
         void update() {
-            mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes);
             mDividerPosition = mProperties.mInitialDividerPosition;
             mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration);
 
@@ -1161,15 +1162,17 @@
                 // When the divider drag handle width is larger than the divider width, the position
                 // of the divider surface is adjusted so that it is large enough to host both the
                 // divider line and the divider drag handle.
-                mDividerSurfaceWidthPx = Math.max(mDividerWidthPx, mHandleWidthPx);
+                mDividerSurfaceWidthPx = Math.max(mProperties.mDividerWidthPx, mHandleWidthPx);
+                dividerSurfacePosition = mProperties.mIsReversedLayout
+                        ? mDividerPosition
+                        : mDividerPosition + mProperties.mDividerWidthPx - mDividerSurfaceWidthPx;
                 dividerSurfacePosition =
-                        mProperties.mIsReversedLayout
-                                ? mDividerPosition
-                                : mDividerPosition + mDividerWidthPx - mDividerSurfaceWidthPx;
-                dividerSurfacePosition = Math.clamp(dividerSurfacePosition, 0,
-                        mProperties.mIsVerticalSplit ? taskBounds.width() : taskBounds.height());
+                        Math.clamp(dividerSurfacePosition, 0,
+                                mProperties.mIsVerticalSplit
+                                        ? taskBounds.width() - mDividerSurfaceWidthPx
+                                        : taskBounds.height() - mDividerSurfaceWidthPx);
             } else {
-                mDividerSurfaceWidthPx = mDividerWidthPx;
+                mDividerSurfaceWidthPx = mProperties.mDividerWidthPx;
                 dividerSurfacePosition = mDividerPosition;
             }
 
@@ -1182,16 +1185,9 @@
             }
 
             // Update divider line position in the surface
-            if (!mProperties.mIsReversedLayout) {
-                final int offset = mDividerPosition - dividerSurfacePosition;
-                mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
-                mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
-            } else {
-                // For reversed layout, the divider line is always at the start of the divider
-                // surface.
-                mDividerLine.setX(0);
-                mDividerLine.setY(0);
-            }
+            final int offset = mDividerPosition - dividerSurfacePosition;
+            mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
+            mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
 
             if (mIsDragging) {
                 updateVeils(t);
@@ -1241,8 +1237,10 @@
             final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
             mDividerLine.setLayoutParams(
                     mProperties.mIsVerticalSplit
-                            ? new FrameLayout.LayoutParams(mDividerWidthPx, taskBounds.height())
-                            : new FrameLayout.LayoutParams(taskBounds.width(), mDividerWidthPx)
+                            ? new FrameLayout.LayoutParams(
+                                    mProperties.mDividerWidthPx, taskBounds.height())
+                            : new FrameLayout.LayoutParams(
+                                    taskBounds.width(), mProperties.mDividerWidthPx)
             );
             if (mProperties.mDividerAttributes.getDividerType()
                     == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
@@ -1352,13 +1350,14 @@
             Rect secondaryBounds;
             if (mProperties.mIsVerticalSplit) {
                 final Rect boundsLeft = new Rect(0, 0, mDividerPosition, taskBounds.height());
-                final Rect boundsRight = new Rect(mDividerPosition + mDividerWidthPx, 0,
+                final Rect boundsRight = new Rect(mDividerPosition + mProperties.mDividerWidthPx, 0,
                         taskBounds.width(), taskBounds.height());
                 primaryBounds = mProperties.mIsReversedLayout ? boundsRight : boundsLeft;
                 secondaryBounds = mProperties.mIsReversedLayout ? boundsLeft : boundsRight;
             } else {
                 final Rect boundsTop = new Rect(0, 0, taskBounds.width(), mDividerPosition);
-                final Rect boundsBottom = new Rect(0, mDividerPosition + mDividerWidthPx,
+                final Rect boundsBottom = new Rect(
+                        0, mDividerPosition + mProperties.mDividerWidthPx,
                         taskBounds.width(), taskBounds.height());
                 primaryBounds = mProperties.mIsReversedLayout ? boundsBottom : boundsTop;
                 secondaryBounds = mProperties.mIsReversedLayout ? boundsTop : boundsBottom;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index c708da9..ee00c4c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -510,7 +510,7 @@
             return;
         }
         final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
-        final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer);
+        final float newRatio = dividerPresenter.calculateNewSplitRatio();
 
         // If the primary container is fully expanded, we should finish all the associated
         // secondary containers.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 20626c7..4515187 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -144,6 +144,7 @@
                 new SplitAttributes.Builder()
                         .setDividerAttributes(DEFAULT_DIVIDER_ATTRIBUTES)
                         .build());
+        final Rect mockTaskBounds = new Rect(0, 0, 2000, 1000);
         final TaskFragmentContainer mockPrimaryContainer =
                 createMockTaskFragmentContainer(
                         mPrimaryContainerToken, new Rect(0, 0, 950, 1000));
@@ -158,7 +159,9 @@
                 DEFAULT_DIVIDER_ATTRIBUTES,
                 mSurfaceControl,
                 getInitialDividerPosition(
-                        mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */),
+                        mockPrimaryContainer, mockSecondaryContainer, mockTaskBounds,
+                        50 /* divideWidthPx */, false /* isDraggableExpandType */,
+                        true /* isVerticalSplit */, false /* isReversedLayout */),
                 true /* isVerticalSplit */,
                 false /* isReversedLayout */,
                 Display.DEFAULT_DISPLAY,
@@ -502,7 +505,6 @@
         assertEquals(
                 0.3f, // Primary is 300px after dragging.
                 DividerPresenter.calculateNewSplitRatio(
-                        mSplitContainer,
                         dividerPosition,
                         taskBounds,
                         dividerWidthPx,
@@ -518,7 +520,6 @@
         assertEquals(
                 DividerPresenter.RATIO_EXPANDED_SECONDARY,
                 DividerPresenter.calculateNewSplitRatio(
-                        mSplitContainer,
                         dividerPosition,
                         taskBounds,
                         dividerWidthPx,
@@ -535,7 +536,6 @@
         assertEquals(
                 0.2f, // Adjusted to the minPosition 200
                 DividerPresenter.calculateNewSplitRatio(
-                        mSplitContainer,
                         dividerPosition,
                         taskBounds,
                         dividerWidthPx,
@@ -569,7 +569,6 @@
                 // After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100].
                 0.7f,
                 DividerPresenter.calculateNewSplitRatio(
-                        mSplitContainer,
                         dividerPosition,
                         taskBounds,
                         dividerWidthPx,
@@ -587,7 +586,6 @@
                 // The primary (bottom) container is expanded
                 DividerPresenter.RATIO_EXPANDED_PRIMARY,
                 DividerPresenter.calculateNewSplitRatio(
-                        mSplitContainer,
                         dividerPosition,
                         taskBounds,
                         dividerWidthPx,
@@ -605,7 +603,6 @@
                 // Adjusted to minPosition 200, so the primary (bottom) container is 800.
                 0.8f,
                 DividerPresenter.calculateNewSplitRatio(
-                        mSplitContainer,
                         dividerPosition,
                         taskBounds,
                         dividerWidthPx,
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 6ca6517..dc022b4 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -69,7 +69,7 @@
 
     /** Returns {@code true} if the transition is opening or closing mode. */
     public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
-        return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+        return isOpeningMode(mode) || isClosingMode(mode);
     }
 
     /** Returns {@code true} if the transition is opening mode. */
@@ -77,6 +77,11 @@
         return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT;
     }
 
+    /** Returns {@code true} if the transition is closing mode. */
+    public static boolean isClosingMode(@TransitionInfo.TransitionMode int mode) {
+        return mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+    }
+
     /** Returns {@code true} if the transition has a display change. */
     public static boolean hasDisplayChange(@NonNull TransitionInfo info) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6fcea1f..b52b0d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -489,6 +489,14 @@
                     // activity windowing mode, and set the task bounds to the final bounds
                     wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
                     wct.setBounds(taskInfo.token, destinationBounds);
+                    // If the animation is only used to apply destination bounds immediately and
+                    // invisibly, then reshow it until the pip is drawn with the bounds.
+                    final PipAnimationController.PipTransitionAnimator<?> animator =
+                            mPipAnimationController.getCurrentAnimator();
+                    if (animator != null && animator.getEndValue().equals(0f)) {
+                        tx.addTransactionCommittedListener(mTransitions.getMainExecutor(),
+                                () -> fadeExistingPip(true /* show */));
+                    }
                 } else {
                     wct.setBounds(taskInfo.token, null /* bounds */);
                 }
@@ -1026,6 +1034,7 @@
         }
         startTransaction.apply();
 
+        int animationDuration = mEnterExitAnimationDuration;
         PipAnimationController.PipTransitionAnimator animator;
         if (enterAnimationType == ANIM_TYPE_BOUNDS) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
@@ -1057,8 +1066,17 @@
                 }
             }
         } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
+            // In case augmentRequest() is unable to apply the entering bounds (e.g. the request
+            // info only contains display change), keep the animation invisible (alpha 0) and
+            // duration 0 to apply the destination bounds. The actual fade-in animation will be
+            // done in onFinishResize() after the bounds are applied.
+            final boolean fadeInAfterOnFinishResize = rotationDelta != Surface.ROTATION_0
+                    && mFixedRotationState == FIXED_ROTATION_CALLBACK;
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
-                    0f, 1f);
+                    0f, fadeInAfterOnFinishResize ? 0f : 1f);
+            if (fadeInAfterOnFinishResize) {
+                animationDuration = 0;
+            }
             mSurfaceTransactionHelper
                     .crop(finishTransaction, leash, destinationBounds)
                     .round(finishTransaction, leash, true /* applyCornerRadius */);
@@ -1068,7 +1086,7 @@
         mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
-                .setDuration(mEnterExitAnimationDuration);
+                .setDuration(animationDuration);
         if (rotationDelta != Surface.ROTATION_0
                 && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // For fixed rotation, the animation destination bounds is in old rotation coordinates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 6224543..6ade81c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1591,7 +1591,7 @@
         public void setHomeTransitionListener(IHomeTransitionListener listener) {
             executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
                     (transitions) -> {
-                        transitions.mHomeTransitionObserver.setHomeTransitionListener(mTransitions,
+                        transitions.mHomeTransitionObserver.setHomeTransitionListener(transitions,
                                 listener);
                     });
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 6901e75..37cdbb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -583,17 +583,20 @@
             } else if (ev.getAction() == ACTION_HOVER_MOVE
                     && MaximizeMenu.Companion.isMaximizeMenuView(id)) {
                 decoration.onMaximizeMenuHoverMove(id, ev);
+                mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
             } else if (ev.getAction() == ACTION_HOVER_EXIT) {
                 if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
                     decoration.onMaximizeWindowHoverExit();
-                } else if (id == R.id.maximize_window || id == R.id.maximize_menu) {
+                } else if (id == R.id.maximize_window
+                        || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
                     // Close menu if not hovering over maximize menu or maximize button after a
                     // delay to give user a chance to re-enter view or to move from one maximize
                     // menu view to another.
                     mMainHandler.postDelayed(mCloseMaximizeWindowRunnable,
                             CLOSE_MAXIMIZE_MENU_DELAY_MS);
-                } else if (MaximizeMenu.Companion.isMaximizeMenuView(id)) {
-                    decoration.onMaximizeMenuHoverExit(id, ev);
+                    if (id != R.id.maximize_window) {
+                        decoration.onMaximizeMenuHoverExit(id, ev);
+                    }
                 }
                 return true;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index 78f0ef7..4f04901 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -88,7 +88,7 @@
     }
 
     fun cancelHoverAnimation() {
-        hoverProgressAnimatorSet.removeAllListeners()
+        hoverProgressAnimatorSet.childAnimations.forEach { it.removeAllListeners() }
         hoverProgressAnimatorSet.cancel()
         progressBar.visibility = View.INVISIBLE
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index ec20471..7ade987 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -23,13 +23,13 @@
 
 val TaskInfo.isTransparentCaptionBarAppearance: Boolean
     get() {
-        val appearance = taskDescription?.systemBarsAppearance ?: 0
+        val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0
         return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
     }
 
 val TaskInfo.isLightCaptionBarAppearance: Boolean
     get() {
-        val appearance = taskDescription?.systemBarsAppearance ?: 0
+        val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0
         return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
index 285e5b6..51b291c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -39,7 +39,7 @@
 class DesktopModeUiEventLoggerTest : ShellTestCase() {
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
     private lateinit var logger: DesktopModeUiEventLogger
-    private val instanceIdSequence = InstanceIdSequence(10)
+    private val instanceIdSequence = InstanceIdSequence(/* instanceIdMax */ 1 shl 20)
 
 
     @Before
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 3ca9b57..a731e53 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
@@ -228,7 +228,7 @@
     public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        taskInfo.taskDescription.setSystemBarsAppearance(
+        taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
                 APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
         final RelayoutParams relayoutParams = new RelayoutParams();
 
@@ -246,7 +246,7 @@
     public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        taskInfo.taskDescription.setSystemBarsAppearance(0);
+        taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
         final RelayoutParams relayoutParams = new RelayoutParams();
 
         DesktopModeWindowDecoration.updateRelayoutParams(
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index f1ee325..eecc741 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -165,6 +165,15 @@
     }
 }
 
+void MouseCursorController::setSkipScreenshot(bool skip) {
+    std::scoped_lock lock(mLock);
+    if (mLocked.skipScreenshot == skip) {
+        return;
+    }
+    mLocked.skipScreenshot = skip;
+    updatePointerLocked();
+}
+
 void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
     std::scoped_lock lock(mLock);
 
@@ -352,6 +361,7 @@
     mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
     mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
     mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
+    mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot);
 
     if (mLocked.pointerAlpha > 0) {
         mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index dc7e8ca..78f6413 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -53,6 +53,9 @@
     void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
     void setStylusHoverMode(bool stylusHoverMode);
 
+    // Set/Unset flag to hide the mouse cursor on the mirrored display
+    void setSkipScreenshot(bool skip);
+
     void updatePointerIcon(PointerIconStyle iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
     void reloadPointerResources(bool getAdditionalMouseResources);
@@ -94,6 +97,7 @@
         PointerIconStyle requestedPointerType;
         PointerIconStyle resolvedPointerType;
 
+        bool skipScreenshot{false};
         bool animating{false};
 
     } mLocked GUARDED_BY(mLock);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index cca1b07..11b27a2 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -286,13 +286,16 @@
     mCursorController.setCustomPointerIcon(icon);
 }
 
-void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
+void PointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) {
     std::scoped_lock lock(getLock());
-    if (skip) {
-        mLocked.displaysToSkipScreenshot.insert(displayId);
-    } else {
-        mLocked.displaysToSkipScreenshot.erase(displayId);
-    }
+    mLocked.displaysToSkipScreenshot.insert(displayId);
+    mCursorController.setSkipScreenshot(true);
+}
+
+void PointerController::clearSkipScreenshotFlags() {
+    std::scoped_lock lock(getLock());
+    mLocked.displaysToSkipScreenshot.clear();
+    mCursorController.setSkipScreenshot(false);
 }
 
 void PointerController::doInactivityTimeout() {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index c6430f7..4d1e1d7 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -66,7 +66,8 @@
     void clearSpots() override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
-    void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
+    void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
+    void clearSkipScreenshotFlags() override;
 
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 2dcb1f1..cbef68e 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -183,12 +183,16 @@
         MyLooper() : Looper(false) {}
         ~MyLooper() = default;
     };
-    sp<MyLooper> mLooper;
     std::thread mThread;
+
+protected:
+    sp<MyLooper> mLooper;
 };
 
-PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
-        mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
+PointerControllerTest::PointerControllerTest()
+      : mPointerSprite(new NiceMock<MockSprite>),
+        mThread(&PointerControllerTest::loopThread, this),
+        mLooper(new MyLooper) {
     mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper));
     mPolicy = new MockPointerControllerPolicyInterface();
 
@@ -339,7 +343,7 @@
     testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 
     // Marking the display to skip screenshot should update sprite as well
-    mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true);
+    mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT);
     EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
 
     // Update spots to sync state with sprite
@@ -348,13 +352,53 @@
     testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 
     // Reset flag and verify again
-    mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false);
+    mPointerController->clearSkipScreenshotFlags();
     EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
     mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
                                  ui::LogicalDisplayId::DEFAULT);
     testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 }
 
+class PointerControllerSkipScreenshotFlagTest
+      : public PointerControllerTest,
+        public testing::WithParamInterface<PointerControllerInterface::ControllerType> {};
+
+TEST_P(PointerControllerSkipScreenshotFlagTest, updatesSkipScreenshotFlag) {
+    sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>);
+    EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite));
+
+    // Create a pointer controller
+    mPointerController =
+            PointerController::create(mPolicy, mLooper, *mSpriteController, GetParam());
+    ensureDisplayViewportIsSet(ui::LogicalDisplayId::DEFAULT);
+
+    // By default skip screenshot flag is not set for the sprite
+    EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false));
+
+    // Update pointer to sync state with sprite
+    mPointerController->setPosition(100, 100);
+    testing::Mock::VerifyAndClearExpectations(testPointerSprite.get());
+
+    // Marking the controller to skip screenshot should update pointer sprite
+    mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT);
+    EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(true));
+
+    // Update pointer to sync state with sprite
+    mPointerController->move(10, 10);
+    testing::Mock::VerifyAndClearExpectations(testPointerSprite.get());
+
+    // Reset flag and verify again
+    mPointerController->clearSkipScreenshotFlags();
+    EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false));
+    mPointerController->move(10, 10);
+    testing::Mock::VerifyAndClearExpectations(testPointerSprite.get());
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest,
+                         PointerControllerSkipScreenshotFlagTest,
+                         testing::Values(PointerControllerInterface::ControllerType::MOUSE,
+                                         PointerControllerInterface::ControllerType::STYLUS));
+
 class PointerControllerWindowInfoListenerTest : public Test {};
 
 TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 293c561..d148afd 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1764,6 +1764,10 @@
     public static native int getForceUse(int usage);
     /** @hide */
     @UnsupportedAppUsage
+    public static native int setDeviceAbsoluteVolumeEnabled(int nativeDeviceType,
+            @NonNull String address, boolean enabled, int streamToDriveAbs);
+    /** @hide */
+    @UnsupportedAppUsage
     public static native int initStreamVolume(int stream, int indexMin, int indexMax);
     @UnsupportedAppUsage
     private static native int setStreamVolumeIndex(int stream, int index, int device);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 70462ef..442ccdc 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -141,10 +141,9 @@
         }
         try {
             return mSessionBinder.sendMediaButton(mContext.getPackageName(), keyEvent);
-        } catch (RemoteException e) {
-            // System is dead. =(
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return false;
     }
 
     /**
@@ -155,9 +154,8 @@
     public @Nullable PlaybackState getPlaybackState() {
         try {
             return mSessionBinder.getPlaybackState();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getPlaybackState.", e);
-            return null;
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -169,9 +167,8 @@
     public @Nullable MediaMetadata getMetadata() {
         try {
             return mSessionBinder.getMetadata();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getMetadata.", e);
-            return null;
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -185,10 +182,9 @@
         try {
             ParceledListSlice list = mSessionBinder.getQueue();
             return list == null ? null : list.getList();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getQueue.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
@@ -197,10 +193,9 @@
     public @Nullable CharSequence getQueueTitle() {
         try {
             return mSessionBinder.getQueueTitle();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getQueueTitle", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
@@ -209,10 +204,9 @@
     public @Nullable Bundle getExtras() {
         try {
             return mSessionBinder.getExtras();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getExtras", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
@@ -232,9 +226,8 @@
     public int getRatingType() {
         try {
             return mSessionBinder.getRatingType();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getRatingType.", e);
-            return Rating.RATING_NONE;
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -246,10 +239,9 @@
     public long getFlags() {
         try {
             return mSessionBinder.getFlags();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getFlags.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return 0;
     }
 
     /** Returns the current playback info for this session. */
@@ -271,10 +263,9 @@
     public @Nullable PendingIntent getSessionActivity() {
         try {
             return mSessionBinder.getLaunchPendingIntent();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getPendingIntent.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
@@ -304,8 +295,8 @@
             //       AppOpsManager usages.
             mSessionBinder.setVolumeTo(mContext.getPackageName(), mContext.getOpPackageName(),
                     value, flags);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling setVolumeTo.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -329,8 +320,8 @@
             //       AppOpsManager usages.
             mSessionBinder.adjustVolume(mContext.getPackageName(), mContext.getOpPackageName(),
                     direction, flags);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -395,8 +386,8 @@
         }
         try {
             mSessionBinder.sendCommand(mContext.getPackageName(), command, args, cb);
-        } catch (RemoteException e) {
-            Log.d(TAG, "Dead object in sendCommand.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -409,8 +400,8 @@
         if (mPackageName == null) {
             try {
                 mPackageName = mSessionBinder.getPackageName();
-            } catch (RemoteException e) {
-                Log.d(TAG, "Dead object in getPackageName.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
         return mPackageName;
@@ -430,8 +421,8 @@
         // Get info from the connected session.
         try {
             mSessionInfo = mSessionBinder.getSessionInfo();
-        } catch (RemoteException e) {
-            Log.d(TAG, "Dead object in getSessionInfo.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
 
         if (mSessionInfo == null) {
@@ -454,8 +445,8 @@
         if (mTag == null) {
             try {
                 mTag = mSessionBinder.getTag();
-            } catch (RemoteException e) {
-                Log.d(TAG, "Dead object in getTag.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
         return mTag;
@@ -485,8 +476,8 @@
             try {
                 mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub);
                 mCbRegistered = true;
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in registerCallback", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
     }
@@ -504,8 +495,8 @@
         if (mCbRegistered && mCallbacks.size() == 0) {
             try {
                 mSessionBinder.unregisterCallback(mCbStub);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in removeCallbackLocked");
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
             mCbRegistered = false;
         }
@@ -641,8 +632,8 @@
         public void prepare() {
             try {
                 mSessionBinder.prepare(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling prepare.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -665,8 +656,8 @@
             }
             try {
                 mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mediaId, extras);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -691,8 +682,8 @@
             }
             try {
                 mSessionBinder.prepareFromSearch(mContext.getPackageName(), query, extras);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling prepare(" + query + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -715,8 +706,8 @@
             }
             try {
                 mSessionBinder.prepareFromUri(mContext.getPackageName(), uri, extras);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling prepare(" + uri + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -726,8 +717,8 @@
         public void play() {
             try {
                 mSessionBinder.play(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling play.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -745,8 +736,8 @@
             }
             try {
                 mSessionBinder.playFromMediaId(mContext.getPackageName(), mediaId, extras);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling play(" + mediaId + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -767,8 +758,8 @@
             }
             try {
                 mSessionBinder.playFromSearch(mContext.getPackageName(), query, extras);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling play(" + query + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -786,8 +777,8 @@
             }
             try {
                 mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling play(" + uri + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -798,8 +789,8 @@
         public void skipToQueueItem(long id) {
             try {
                 mSessionBinder.skipToQueueItem(mContext.getPackageName(), id);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -810,8 +801,8 @@
         public void pause() {
             try {
                 mSessionBinder.pause(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling pause.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -822,8 +813,8 @@
         public void stop() {
             try {
                 mSessionBinder.stop(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling stop.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -835,8 +826,8 @@
         public void seekTo(long pos) {
             try {
                 mSessionBinder.seekTo(mContext.getPackageName(), pos);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling seekTo.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -847,8 +838,8 @@
         public void fastForward() {
             try {
                 mSessionBinder.fastForward(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling fastForward.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -858,8 +849,8 @@
         public void skipToNext() {
             try {
                 mSessionBinder.next(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling next.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -870,8 +861,8 @@
         public void rewind() {
             try {
                 mSessionBinder.rewind(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling rewind.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -881,8 +872,8 @@
         public void skipToPrevious() {
             try {
                 mSessionBinder.previous(mContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling previous.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -896,8 +887,8 @@
         public void setRating(Rating rating) {
             try {
                 mSessionBinder.rate(mContext.getPackageName(), rating);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling rate.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -914,8 +905,8 @@
             }
             try {
                 mSessionBinder.setPlaybackSpeed(mContext.getPackageName(), speed);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error calling setPlaybackSpeed.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
 
@@ -949,8 +940,8 @@
             }
             try {
                 mSessionBinder.sendCustomAction(mContext.getPackageName(), action, args);
-            } catch (RemoteException e) {
-                Log.d(TAG, "Dead object in sendCustomAction.", e);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
             }
         }
     }
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index a33e225..055ccbc 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -27,6 +27,7 @@
     field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.SHOW_CUSTOMIZED_RESOLVER) public static final String ACTION_SHOW_NFC_RESOLVER = "android.nfc.action.SHOW_NFC_RESOLVER";
     field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String EXTRA_RESOLVE_INFOS = "android.nfc.extra.RESOLVE_INFOS";
+    field @FlaggedApi("android.nfc.nfc_set_default_disc_tech") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final int FLAG_SET_DEFAULT_TECH = 1073741824; // 0x40000000
     field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
     field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3
     field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 698df28..1dfc81e 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -340,7 +340,8 @@
     public static final int FLAG_READER_NFC_BARCODE = 0x10;
 
     /** @hide */
-    @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+    @IntDef(flag = true, value = {
+        FLAG_SET_DEFAULT_TECH,
         FLAG_READER_KEEP,
         FLAG_READER_DISABLE,
         FLAG_READER_NFC_A,
@@ -438,7 +439,8 @@
     public static final int FLAG_USE_ALL_TECH = 0xff;
 
     /** @hide */
-    @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+    @IntDef(flag = true, value = {
+        FLAG_SET_DEFAULT_TECH,
         FLAG_LISTEN_KEEP,
         FLAG_LISTEN_DISABLE,
         FLAG_LISTEN_NFC_PASSIVE_A,
@@ -449,6 +451,18 @@
     public @interface ListenTechnology {}
 
     /**
+     * Flag used in {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag changes the default listen or poll tech.
+     * Only available to privileged apps.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH)
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    public static final int FLAG_SET_DEFAULT_TECH = 0x40000000;
+
+    /**
      * @hide
      * @removed
      */
@@ -1874,14 +1888,6 @@
     public void setDiscoveryTechnology(@NonNull Activity activity,
             @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
 
-        // A special treatment of the _KEEP flags
-        if ((listenTechnology & FLAG_LISTEN_KEEP) != 0) {
-            listenTechnology = -1;
-        }
-        if ((pollTechnology & FLAG_READER_KEEP) != 0) {
-            pollTechnology = -1;
-        }
-
         if (listenTechnology == FLAG_LISTEN_DISABLE) {
             synchronized (sLock) {
                 if (!sHasNfcFeature) {
@@ -1901,7 +1907,25 @@
                 }
             }
         }
-        mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+    /*
+     * Privileged FLAG to set technology mask for all data processed by NFC controller
+     * Note: Use with caution! The app is responsible for ensuring that the discovery
+     * technology mask is returned to default.
+     * Note: FLAG_USE_ALL_TECH used with _KEEP flags will reset the technolody to android default
+     */
+        if (Flags.nfcSetDefaultDiscTech()
+                && ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH
+                || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {
+            Binder token = new Binder();
+            try {
+                NfcAdapter.sService.updateDiscoveryTechnology(token,
+                        pollTechnology, listenTechnology);
+            } catch (RemoteException e) {
+                attemptDeadServiceRecovery(e);
+            }
+        } else {
+            mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+        }
     }
 
     /**
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index cb2a48c..b242a76 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -101,3 +101,12 @@
     description: "Enable nfc state change API"
     bug: "319934052"
 }
+
+flag {
+    name: "nfc_set_default_disc_tech"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC set default disc tech API"
+    bug: "321311407"
+}
+
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index b43b5f3..373b3e8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -38,7 +38,6 @@
 import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.model.get.ProviderInfo
-import java.lang.Exception
 
 /**
  * Aggregates common display information used for the Biometric Flow.
@@ -121,11 +120,11 @@
     getBiometricCancellationSignal: () -> CancellationSignal,
     getRequestDisplayInfo: RequestDisplayInfo? = null,
     getProviderInfoList: List<ProviderInfo>? = null,
-    getProviderDisplayInfo: ProviderDisplayInfo? = null,
-) {
+    getProviderDisplayInfo: ProviderDisplayInfo? = null
+): Boolean {
     if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
         // Screen is already up, do not re-launch
-        return
+        return false
     }
     onBiometricPromptStateChange(BiometricPromptState.PENDING)
     val biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(
@@ -137,7 +136,7 @@
 
     if (biometricDisplayInfo == null) {
         onBiometricFailureFallback(BiometricFlowType.GET)
-        return
+        return false
     }
 
     val callback: BiometricPrompt.AuthenticationCallback =
@@ -146,7 +145,7 @@
             getBiometricPromptState)
 
     Log.d(TAG, "The BiometricPrompt API call begins for Get.")
-    runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+    return runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
         onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish,
         getBiometricCancellationSignal)
 }
@@ -169,11 +168,11 @@
     getBiometricCancellationSignal: () -> CancellationSignal,
     createRequestDisplayInfo: com.android.credentialmanager.createflow
     .RequestDisplayInfo? = null,
-    createProviderInfo: EnabledProviderInfo? = null,
-) {
+    createProviderInfo: EnabledProviderInfo? = null
+): Boolean {
     if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
         // Screen is already up, do not re-launch
-        return
+        return false
     }
     onBiometricPromptStateChange(BiometricPromptState.PENDING)
     val biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
@@ -184,7 +183,7 @@
 
     if (biometricDisplayInfo == null) {
         onBiometricFailureFallback(BiometricFlowType.CREATE)
-        return
+        return false
     }
 
     val callback: BiometricPrompt.AuthenticationCallback =
@@ -193,7 +192,7 @@
             getBiometricPromptState)
 
     Log.d(TAG, "The BiometricPrompt API call begins for Create.")
-    runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+    return runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
         onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish,
         getBiometricCancellationSignal)
 }
@@ -206,19 +205,19 @@
  * only device credentials are requested.
  */
 private fun runBiometricFlow(
-    context: Context,
-    biometricDisplayInfo: BiometricDisplayInfo,
-    callback: BiometricPrompt.AuthenticationCallback,
-    openMoreOptionsPage: () -> Unit,
-    onBiometricFailureFallback: (BiometricFlowType) -> Unit,
-    biometricFlowType: BiometricFlowType,
-    onCancelFlowAndFinish: () -> Unit,
-    getBiometricCancellationSignal: () -> CancellationSignal,
-) {
+        context: Context,
+        biometricDisplayInfo: BiometricDisplayInfo,
+        callback: BiometricPrompt.AuthenticationCallback,
+        openMoreOptionsPage: () -> Unit,
+        onBiometricFailureFallback: (BiometricFlowType) -> Unit,
+        biometricFlowType: BiometricFlowType,
+        onCancelFlowAndFinish: () -> Unit,
+        getBiometricCancellationSignal: () -> CancellationSignal
+): Boolean {
     try {
         if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
             onBiometricFailureFallback(biometricFlowType)
-            return
+            return false
         }
 
         val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo,
@@ -239,7 +238,9 @@
     } catch (e: IllegalArgumentException) {
         Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
         onBiometricFailureFallback(biometricFlowType)
+        return false
     }
+    return true
 }
 
 private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 7d61f73..4993a1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -123,7 +123,8 @@
                                 onBiometricPromptStateChange =
                                 viewModel::onBiometricPromptStateChange,
                                 getBiometricCancellationSignal =
-                                viewModel::getBiometricCancellationSignal
+                                viewModel::getBiometricCancellationSignal,
+                                onLog = { viewModel.logUiEvent(it) },
                             )
                         CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard(
                                 requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
@@ -642,12 +643,13 @@
     getBiometricPromptState: () -> BiometricPromptState,
     onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
     getBiometricCancellationSignal: () -> CancellationSignal,
+    onLog: @Composable (UiEventEnum) -> Unit
 ) {
     if (biometricEntry == null) {
         fallbackToOriginalFlow(BiometricFlowType.CREATE)
         return
     }
-    runBiometricFlowForCreate(
+    val biometricFlowCalled = runBiometricFlowForCreate(
         biometricEntry = biometricEntry,
         context = LocalContext.current,
         openMoreOptionsPage = onMoreOptionSelected,
@@ -659,6 +661,9 @@
         createProviderInfo = enabledProviderInfo,
         onBiometricFailureFallback = fallbackToOriginalFlow,
         onIllegalStateAndFinish = onIllegalScreenStateAndFinish,
-        getBiometricCancellationSignal = getBiometricCancellationSignal,
+        getBiometricCancellationSignal = getBiometricCancellationSignal
     )
+    if (biometricFlowCalled) {
+        onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED)
+    }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ba61b90..517ad00 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -166,7 +166,8 @@
                                 onBiometricPromptStateChange =
                                 viewModel::onBiometricPromptStateChange,
                                 getBiometricCancellationSignal =
-                                viewModel::getBiometricCancellationSignal
+                                viewModel::getBiometricCancellationSignal,
+                                onLog = { viewModel.logUiEvent(it) },
                             )
                         } else if (credmanBiometricApiEnabled() &&
                                 getCredentialUiState.currentScreenState
@@ -260,12 +261,13 @@
     getBiometricPromptState: () -> BiometricPromptState,
     onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
     getBiometricCancellationSignal: () -> CancellationSignal,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     if (biometricEntry == null) {
         fallbackToOriginalFlow(BiometricFlowType.GET)
         return
     }
-    runBiometricFlowForGet(
+    val biometricFlowCalled = runBiometricFlowForGet(
         biometricEntry = biometricEntry,
         context = LocalContext.current,
         openMoreOptionsPage = onMoreOptionSelected,
@@ -280,6 +282,9 @@
         onBiometricFailureFallback = fallbackToOriginalFlow,
         getBiometricCancellationSignal = getBiometricCancellationSignal
     )
+    if (biometricFlowCalled) {
+        onLog(GetCredentialEvent.CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED)
+    }
 }
 
 /** Draws the primary credential selection page, used in Android U. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
index daa42be..39f2fce 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
@@ -17,6 +17,7 @@
 
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
 
 enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
 
@@ -52,7 +53,10 @@
     CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327),
 
     @UiEvent(doc = "The more about passkeys intro card is visible on screen.")
-    CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328);
+    CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328),
+
+    @UiEvent(doc = "The single tap biometric flow is launched.")
+    CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED(RESERVE_NEW_UI_EVENT_ID);
 
     override fun getId(): Int {
         return this.id
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
index 8de8895..89fd72c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
@@ -17,6 +17,7 @@
 
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
 
 enum class GetCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
 
@@ -54,7 +55,10 @@
     CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD(1341),
 
     @UiEvent(doc = "The all sign in option card is visible on screen.")
-    CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342);
+    CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342),
+
+    @UiEvent(doc = "The single tap biometric flow is launched.")
+    CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED(RESERVE_NEW_UI_EVENT_ID);
 
     override fun getId(): Int {
         return this.id
diff --git a/packages/SettingsLib/Color/res/values/colors.xml b/packages/SettingsLib/Color/res/values/colors.xml
index ef0dd1b..b0b9b10 100644
--- a/packages/SettingsLib/Color/res/values/colors.xml
+++ b/packages/SettingsLib/Color/res/values/colors.xml
@@ -17,7 +17,6 @@
 
 <resources>
     <!-- Dynamic colors-->
-    <color name="settingslib_color_blue700">#0B57D0</color>
     <color name="settingslib_color_blue600">#1a73e8</color>
     <color name="settingslib_color_blue400">#669df6</color>
     <color name="settingslib_color_blue300">#8ab4f8</color>
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index c3a91a2..cd8f584 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -47,6 +47,7 @@
     aconfig_declarations: "settingslib_illustrationpreference_flags",
 
     min_sdk_version: "30",
+    sdk_version: "system_current",
 
     apex_available: [
         "//apex_available:platform",
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index bc3488fc..0447ef8 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -56,9 +56,6 @@
                 ".black",
                 android.R.color.white);
         map.put(
-                ".blue200",
-                R.color.settingslib_color_blue700);
-        map.put(
                 ".blue400",
                 R.color.settingslib_color_blue600);
         map.put(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt
index e91fa65..e9f9689 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt
@@ -18,9 +18,9 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 @Composable
 fun LifecycleEffect(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
index 3991f26..0b1c92d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
@@ -24,7 +24,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 /**
  * An effect for detecting presses of the system back button, and the back event will not be
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
index ee24a09..007f47b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
@@ -21,8 +21,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.slice.widget.SliceLiveData
 import androidx.slice.widget.SliceView
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index de080e3..022dded 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -38,6 +38,7 @@
 
 data class AlertDialogButton(
     val text: String,
+    val enabled: Boolean = true,
     val onClick: () -> Unit = {},
 )
 
@@ -114,6 +115,7 @@
             close()
             button.onClick()
         },
+        enabled = button.enabled,
     ) {
         Text(button.text)
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
index b471e50..bdbe62c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -22,6 +22,7 @@
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.MenuAnchorType
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -65,7 +66,7 @@
         OutlinedTextField(
             // The `menuAnchor` modifier must be passed to the text field for correctness.
             modifier = Modifier
-                .menuAnchor()
+                .menuAnchor(MenuAnchorType.PrimaryNotEditable)
                 .fillMaxWidth(),
             value = text,
             onValueChange = { },
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt
index fe7baff..8b0efff 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt
@@ -18,9 +18,9 @@
 
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
index 9468f95..20ea397 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
@@ -20,6 +20,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.performClick
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -67,7 +69,18 @@
             rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT))
         }
 
-        composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed()
+        composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun confirmButton_disabled() {
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(
+                confirmButton = AlertDialogButton(text = CONFIRM_TEXT, enabled = false)
+            )
+        }
+
+        composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed().assertIsNotEnabled()
     }
 
     @Test
@@ -90,7 +103,18 @@
             rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT))
         }
 
-        composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed()
+        composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun dismissButton_disabled() {
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(
+                dismissButton = AlertDialogButton(text = DISMISS_TEXT, enabled = false)
+            )
+        }
+
+        composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed().assertIsNotEnabled()
     }
 
     @Test
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 977615b..f95cfc3 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -30,22 +30,24 @@
 @Composable
 fun MoreOptionsScope.RestrictedMenuItem(
     text: String,
+    enabled: Boolean = true,
     restrictions: Restrictions,
     onClick: () -> Unit,
 ) {
-    RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
+    RestrictedMenuItemImpl(text, enabled, restrictions, onClick, ::RestrictionsProviderImpl)
 }
 
 @VisibleForTesting
 @Composable
 internal fun MoreOptionsScope.RestrictedMenuItemImpl(
     text: String,
+    enabled: Boolean = true,
     restrictions: Restrictions,
     onClick: () -> Unit,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
-    MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
+    MenuItem(text = text, enabled = enabled && restrictedMode !== BaseUserRestricted) {
         when (restrictedMode) {
             is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
             is BlockedByEcm -> restrictedMode.showRestrictedSettingsDetails()
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 556adc7..4068bce 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -49,6 +49,15 @@
     private var menuItemOnClickIsCalled = false
 
     @Test
+    fun whenDisabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions, enabled = false)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    @Test
     fun whenRestrictionsKeysIsEmpty_enabled() {
         val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
 
@@ -153,13 +162,14 @@
         assertThat(menuItemOnClickIsCalled).isFalse()
     }
 
-    private fun setContent(restrictions: Restrictions) {
+    private fun setContent(restrictions: Restrictions, enabled: Boolean = true) {
         val fakeMoreOptionsScope = object : MoreOptionsScope() {
             override fun dismiss() {}
         }
         composeTestRule.setContent {
             fakeMoreOptionsScope.RestrictedMenuItemImpl(
                 text = TEXT,
+                enabled = enabled,
                 restrictions = restrictions,
                 onClick = { menuItemOnClickIsCalled = true },
                 restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index ab04904..470cdee 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -32,7 +32,7 @@
 
     <!-- Usage graph dimens -->
     <dimen name="usage_graph_margin_top_bottom">9dp</dimen>
-    <dimen name="usage_graph_labels_width">56dp</dimen>
+    <dimen name="usage_graph_labels_width">60dp</dimen>
 
     <dimen name="usage_graph_divider_size">1dp</dimen>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 869fb7f..7081195 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -81,7 +81,7 @@
                     localMediaManager.unregisterCallback(callback)
                 }
             }
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+            .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 0)
 
     override val currentConnectedDevice: StateFlow<MediaDevice?> =
         merge(devicesChanges, mediaDevicesUpdates)
@@ -89,8 +89,8 @@
             .onStart { emit(localMediaManager.currentConnectedDevice) }
             .stateIn(
                 coroutineScope,
-                SharingStarted.WhileSubscribed(),
-                localMediaManager.currentConnectedDevice
+                SharingStarted.Eagerly,
+                localMediaManager.currentConnectedDevice,
             )
 
     private sealed interface DevicesUpdate {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c8992c3..04922d6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -64,7 +64,6 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
-import java.io.InputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.file.Files;
@@ -86,13 +85,13 @@
 // FOR ACONFIGD TEST MISSION AND ROLLOUT
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
 import android.util.proto.ProtoInputStream;
 import android.aconfigd.Aconfigd.StorageRequestMessage;
 import android.aconfigd.Aconfigd.StorageRequestMessages;
 import android.aconfigd.Aconfigd.StorageReturnMessage;
 import android.aconfigd.Aconfigd.StorageReturnMessages;
-import android.aconfigd.AconfigdClientSocket;
-import android.aconfigd.AconfigdFlagInfo;
 import android.aconfigd.AconfigdJavaUtils;
 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
 /**
@@ -266,10 +265,6 @@
     @NonNull
     private Map<String, Map<String, String>> mNamespaceDefaults;
 
-    // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
-    @NonNull
-    private Map<String, AconfigdFlagInfo> mAconfigDefaultFlags;
-
     public static final int SETTINGS_TYPE_GLOBAL = 0;
     public static final int SETTINGS_TYPE_SYSTEM = 1;
     public static final int SETTINGS_TYPE_SECURE = 2;
@@ -339,13 +334,8 @@
                 + settingTypeToString(getTypeFromKey(key)) + "]";
     }
 
-    public SettingsState(
-            Context context,
-            Object lock,
-            File file,
-            int key,
-            int maxBytesPerAppPackage,
-            Looper looper) {
+    public SettingsState(Context context, Object lock, File file, int key,
+            int maxBytesPerAppPackage, Looper looper) {
         // It is important that we use the same lock as the settings provider
         // to ensure multiple mutations on this state are atomically persisted
         // as the async persistence should be blocked while we make changes.
@@ -363,15 +353,12 @@
             mPackageToMemoryUsage = null;
         }
 
-        mHistoricalOperations =
-                Build.IS_DEBUGGABLE ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
+        mHistoricalOperations = Build.IS_DEBUGGABLE
+                ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
 
         mNamespaceDefaults = new HashMap<>();
-        mAconfigDefaultFlags = new HashMap<>();
 
         ProtoOutputStream requests = null;
-        Map<String, AconfigdFlagInfo> aconfigFlagMap = new HashMap<>();
-
         synchronized (mLock) {
             readStateSyncLocked();
 
@@ -388,114 +375,39 @@
                 }
             }
 
-            if (enableAconfigStorageDaemon()) {
-                if (isConfigSettingsKey(mKey)) {
-                    aconfigFlagMap = getAllAconfigFlagsFromSettings();
-                }
-            }
-
             if (isConfigSettingsKey(mKey)) {
-                requests = handleBulkSyncToNewStorage(aconfigFlagMap);
+                requests = handleBulkSyncToNewStorage();
             }
         }
 
-        if (enableAconfigStorageDaemon()) {
-            if (isConfigSettingsKey(mKey)){
-                AconfigdClientSocket localSocket = AconfigdJavaUtils.getAconfigdClientSocket();
-                if (requests != null) {
-                    InputStream res = localSocket.send(requests.getBytes());
-                    if (res == null) {
-                        Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
-                    }
-                }
-                // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
-                if (mSettings.get("aconfigd_marker/bulk_synced").value.equals("true")
-                        && requests == null) {
-                    Map<String, AconfigdFlagInfo> aconfigdFlagMap =
-                            AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
-                    compareFlagValueInNewStorage(
-                            aconfigFlagMap,
-                            mAconfigDefaultFlags,
-                            aconfigdFlagMap);
-                }
+        if (requests != null) {
+            LocalSocket client = new LocalSocket();
+            try{
+                client.connect(new LocalSocketAddress(
+                    "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+                Slog.d(LOG_TAG, "connected to aconfigd socket");
+            } catch (IOException ioe) {
+                Slog.e(LOG_TAG, "failed to connect to aconfigd socket", ioe);
+                return;
             }
+            AconfigdJavaUtils.sendAconfigdRequests(client, requests);
         }
     }
 
-    // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
-    public int compareFlagValueInNewStorage(
-            Map<String, AconfigdFlagInfo> settingFlagMap,
-            Map<String, AconfigdFlagInfo> defaultFlagMap,
-            Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
-
-        // Get all defaults from the default map. The mSettings may not contain
-        // all flags, since it only contains updated flags.
-        int diffNum = 0;
-        for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) {
-            String key = entry.getKey();
-            AconfigdFlagInfo flag = entry.getValue();
-            if (settingFlagMap.containsKey(key)) {
-                flag.merge(settingFlagMap.get(key));
-            }
-
-            AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key);
-            if (aconfigdFlag == null) {
-                Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key));
-                diffNum++;
-                continue;
-            }
-            String diff = flag.dumpDiff(aconfigdFlag);
-            if (!diff.isEmpty()) {
-                Slog.w(
-                        LOG_TAG,
-                        String.format(
-                                "Flag %s is different in Settings and aconfig: %s", key, diff));
-                diffNum++;
-            }
-        }
-
-        for (String key : aconfigdFlagMap.keySet()) {
-            if (defaultFlagMap.containsKey(key)) continue;
-            Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key));
-            diffNum++;
-        }
-
-        if (diffNum == 0) {
-            Slog.i(LOG_TAG, "Settings and new storage have same flags.");
-        }
-        return diffNum;
-    }
-
-    @GuardedBy("mLock")
-    public Map<String, AconfigdFlagInfo> getAllAconfigFlagsFromSettings() {
-        Map<String, AconfigdFlagInfo> ret = new HashMap<>();
-        int numSettings = mSettings.size();
-        int num_requests = 0;
-        for (int i = 0; i < numSettings; i++) {
-            String name = mSettings.keyAt(i);
-            Setting setting = mSettings.valueAt(i);
-            AconfigdFlagInfo flag =
-                    getFlagOverrideToSync(name, setting.getValue());
-            if (flag == null) {
-                continue;
-            }
-            String fullFlagName = flag.getFullFlagName();
-            AconfigdFlagInfo prev = ret.putIfAbsent(fullFlagName,flag);
-            if (prev != null) {
-                prev.merge(flag);
-            }
-            ++num_requests;
-        }
-        Slog.i(LOG_TAG, num_requests + " flag override requests created");
-        return ret;
+    // TODO(b/341764371): migrate aconfig flag push to GMS core
+    public static class FlagOverrideToSync {
+        public String packageName;
+        public String flagName;
+        public String flagValue;
+        public boolean isLocal;
     }
 
     // TODO(b/341764371): migrate aconfig flag push to GMS core
     @VisibleForTesting
     @GuardedBy("mLock")
-    public AconfigdFlagInfo getFlagOverrideToSync(String name, String value) {
+    public FlagOverrideToSync getFlagOverrideToSync(String name, String value) {
         int slashIdx = name.indexOf("/");
-        if (slashIdx <= 0 || slashIdx >= name.length() - 1) {
+        if (slashIdx <= 0 || slashIdx >= name.length()-1) {
             Slog.e(LOG_TAG, "invalid flag name " + name);
             return null;
         }
@@ -518,9 +430,8 @@
         }
 
         String aconfigName = namespace + "/" + fullFlagName;
-        boolean isAconfig =
-                mNamespaceDefaults.containsKey(namespace)
-                        && mNamespaceDefaults.get(namespace).containsKey(aconfigName);
+        boolean isAconfig = mNamespaceDefaults.containsKey(namespace)
+                            && mNamespaceDefaults.get(namespace).containsKey(aconfigName);
         if (!isAconfig) {
             return null;
         }
@@ -532,30 +443,25 @@
             return null;
         }
 
-        AconfigdFlagInfo.Builder builder = AconfigdFlagInfo.newBuilder()
-                        .setPackageName(fullFlagName.substring(0, dotIdx))
-                        .setFlagName(fullFlagName.substring(dotIdx + 1))
-                        .setDefaultFlagValue(mNamespaceDefaults.get(namespace).get(aconfigName));
-
-        if (isLocal) {
-            builder.setHasLocalOverride(isLocal).setBootFlagValue(value).setLocalFlagValue(value);
-        } else {
-            builder.setHasServerOverride(true).setServerFlagValue(value).setBootFlagValue(value);
-        }
-        return builder.build();
+        FlagOverrideToSync flag = new FlagOverrideToSync();
+        flag.packageName = fullFlagName.substring(0, dotIdx);
+        flag.flagName = fullFlagName.substring(dotIdx + 1);
+        flag.isLocal = isLocal;
+        flag.flagValue = value;
+        return flag;
     }
 
 
     // TODO(b/341764371): migrate aconfig flag push to GMS core
     @VisibleForTesting
     @GuardedBy("mLock")
-    public ProtoOutputStream handleBulkSyncToNewStorage(
-            Map<String, AconfigdFlagInfo> aconfigFlagMap) {
+    public ProtoOutputStream handleBulkSyncToNewStorage() {
         // get marker or add marker if it does not exist
         final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced");
         Setting markerSetting = mSettings.get(bulkSyncMarkerName);
         if (markerSetting == null) {
-            markerSetting = new Setting(bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
+            markerSetting = new Setting(
+                bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
             mSettings.put(bulkSyncMarkerName, markerSetting);
         }
 
@@ -573,19 +479,24 @@
                 AconfigdJavaUtils.writeResetStorageRequest(requests);
 
                 // loop over all settings and add flag override requests
-                for (AconfigdFlagInfo flag : aconfigFlagMap.values()) {
-                    String value =
-                            flag.getHasLocalOverride()
-                                    ? flag.getLocalFlagValue()
-                                    : flag.getServerFlagValue();
+                final int numSettings = mSettings.size();
+                int num_requests = 0;
+                for (int i = 0; i < numSettings; i++) {
+                    String name = mSettings.keyAt(i);
+                    Setting setting = mSettings.valueAt(i);
+                    FlagOverrideToSync flag =
+                            getFlagOverrideToSync(name, setting.getValue());
+                    if (flag == null) {
+                        continue;
+                    }
+                    ++num_requests;
                     AconfigdJavaUtils.writeFlagOverrideRequest(
-                            requests,
-                            flag.getPackageName(),
-                            flag.getFlagName(),
-                            value,
-                            flag.getHasLocalOverride());
+                        requests, flag.packageName, flag.flagName, flag.flagValue,
+                        flag.isLocal);
                 }
 
+                Slog.i(LOG_TAG, num_requests + " flag override requests created");
+
                 // mark sync has been done
                 markerSetting.value = "true";
                 scheduleWriteIfNeededLocked();
@@ -602,14 +513,14 @@
                 return null;
             }
         }
+
     }
 
     @GuardedBy("mLock")
     private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
         for (String fileName : filePaths) {
             try (FileInputStream inputStream = new FileInputStream(fileName)) {
-                loadAconfigDefaultValues(
-                        inputStream.readAllBytes(), mNamespaceDefaults, mAconfigDefaultFlags);
+                loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "failed to read protobuf", e);
             }
@@ -655,30 +566,21 @@
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    public static void loadAconfigDefaultValues(
-            byte[] fileContents,
-            @NonNull Map<String, Map<String, String>> defaultMap,
-            @NonNull Map<String, AconfigdFlagInfo> flagInfoDefault) {
+    public static void loadAconfigDefaultValues(byte[] fileContents,
+            @NonNull Map<String, Map<String, String>> defaultMap) {
         try {
-            parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+            parsed_flags parsedFlags =
+                    parsed_flags.parseFrom(fileContents);
             for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
                 if (!defaultMap.containsKey(flag.getNamespace())) {
                     Map<String, String> defaults = new HashMap<>();
                     defaultMap.put(flag.getNamespace(), defaults);
                 }
-                String fullFlagName = flag.getPackage() + "." + flag.getName();
-                String flagName = flag.getNamespace() + "/" + fullFlagName;
-                String flagValue = flag.getState() == flag_state.ENABLED ? "true" : "false";
+                String flagName = flag.getNamespace()
+                        + "/" + flag.getPackage() + "." + flag.getName();
+                String flagValue = flag.getState() == flag_state.ENABLED
+                        ? "true" : "false";
                 defaultMap.get(flag.getNamespace()).put(flagName, flagValue);
-                if (!flagInfoDefault.containsKey(fullFlagName)) {
-                    flagInfoDefault.put(
-                            fullFlagName,
-                            AconfigdFlagInfo.newBuilder()
-                                    .setPackageName(flag.getPackage())
-                                    .setFlagName(flag.getName())
-                                    .setDefaultFlagValue(flagValue)
-                                    .build());
-                }
             }
         } catch (IOException e) {
             Slog.e(LOG_TAG, "failed to parse protobuf", e);
@@ -1744,6 +1646,7 @@
                         }
                     }
                 }
+
                 mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
                         fromSystem, id, isPreservedInRestore));
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index ca1e4c1..e4898da 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -27,11 +27,11 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile;
+import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
 import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
-import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
 import com.android.bedstead.harrier.annotations.RequireFeature;
 import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
 import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 256b999..244c8c4 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -24,13 +24,13 @@
 import android.aconfig.Aconfig;
 import android.aconfig.Aconfig.parsed_flag;
 import android.aconfig.Aconfig.parsed_flags;
-import android.aconfigd.AconfigdFlagInfo;
 import android.os.Looper;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
+import com.android.providers.settings.SettingsState.FlagOverrideToSync;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -145,32 +145,16 @@
                         .setState(Aconfig.flag_state.ENABLED)
                         .setPermission(Aconfig.flag_permission.READ_WRITE))
                 .build();
-        
-        AconfigdFlagInfo flag1 = AconfigdFlagInfo.newBuilder()
-                                                .setPackageName("com.android.flags")
-                                                .setFlagName("flag1")
-                                                .setDefaultFlagValue("false")
-                                                .build();
-        AconfigdFlagInfo flag2 = AconfigdFlagInfo.newBuilder()
-                                                .setPackageName("com.android.flags")
-                                                .setFlagName("flag2")
-                                                .setDefaultFlagValue("true")
-                                                .build();
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
 
         synchronized (lock) {
             Map<String, Map<String, String>> defaults = new HashMap<>();
-            settingsState.loadAconfigDefaultValues(
-                flags.toByteArray(), defaults, flagInfoDefault);
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
             Map<String, String> namespaceDefaults = defaults.get("test_namespace");
             assertEquals(2, namespaceDefaults.keySet().size());
 
             assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1"));
             assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2"));
         }
-
-        assertEquals(flag1, flagInfoDefault.get(flag1.getFullFlagName()));
-        assertEquals(flag2, flagInfoDefault.get(flag2.getFullFlagName()));
     }
 
     @Test
@@ -181,8 +165,6 @@
                 InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
-
         parsed_flags flags = parsed_flags
                 .newBuilder()
                 .addParsedFlag(parsed_flag
@@ -195,8 +177,7 @@
 
         synchronized (lock) {
             Map<String, Map<String, String>> defaults = new HashMap<>();
-            settingsState.loadAconfigDefaultValues(
-                flags.toByteArray(), defaults, flagInfoDefault);
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
 
             Map<String, String> namespaceDefaults = defaults.get("test_namespace");
             assertEquals(null, namespaceDefaults);
@@ -223,12 +204,10 @@
                         .setState(Aconfig.flag_state.DISABLED)
                         .setPermission(Aconfig.flag_permission.READ_WRITE))
                 .build();
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
 
         synchronized (lock) {
             Map<String, Map<String, String>> defaults = new HashMap<>();
-            settingsState.loadAconfigDefaultValues(
-                flags.toByteArray(), defaults, flagInfoDefault);
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
             settingsState.addAconfigDefaultValuesFromMap(defaults);
 
             settingsState.insertSettingLocked("test_namespace/com.android.flags.flag5",
@@ -259,10 +238,8 @@
     @Test
     public void testInvalidAconfigProtoDoesNotCrash() {
         Map<String, Map<String, String>> defaults = new HashMap<>();
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
         SettingsState settingsState = getSettingStateObject();
-        settingsState.loadAconfigDefaultValues(
-            "invalid protobuf".getBytes(), defaults, flagInfoDefault);
+        settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
     }
 
     @Test
@@ -782,8 +759,6 @@
         Map<String, String> keyValues =
                 Map.of("test_namespace/com.android.flags.flag3", "true");
 
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
-
         parsed_flags flags = parsed_flags
                 .newBuilder()
                 .addParsedFlag(parsed_flag
@@ -799,8 +774,7 @@
 
         synchronized (mLock) {
             settingsState.loadAconfigDefaultValues(
-                    flags.toByteArray(),
-                    settingsState.getAconfigDefaultValues(), flagInfoDefault);
+                    flags.toByteArray(), settingsState.getAconfigDefaultValues());
             List<String> updates =
                     settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
             assertEquals(1, updates.size());
@@ -866,13 +840,10 @@
                         .setState(Aconfig.flag_state.DISABLED)
                         .setPermission(Aconfig.flag_permission.READ_WRITE))
                 .build();
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
 
         synchronized (mLock) {
             settingsState.loadAconfigDefaultValues(
-                    flags.toByteArray(),
-                    settingsState.getAconfigDefaultValues(),
-                    flagInfoDefault);
+                    flags.toByteArray(), settingsState.getAconfigDefaultValues());
             List<String> updates =
                     settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
             assertEquals(3, updates.size());
@@ -1002,12 +973,10 @@
                         .setState(Aconfig.flag_state.DISABLED)
                         .setPermission(Aconfig.flag_permission.READ_WRITE))
                 .build();
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
 
         synchronized (lock) {
             Map<String, Map<String, String>> defaults = new HashMap<>();
-            settingsState.loadAconfigDefaultValues(
-                flags.toByteArray(), defaults, flagInfoDefault);
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
             Map<String, String> namespaceDefaults = defaults.get("test_namespace");
             assertEquals(1, namespaceDefaults.keySet().size());
             settingsState.addAconfigDefaultValuesFromMap(defaults);
@@ -1022,28 +991,22 @@
             "some_namespace/some_flag", "false") == null);
 
         // server override
-        AconfigdFlagInfo flag = settingsState.getFlagOverrideToSync(
+        FlagOverrideToSync flag = settingsState.getFlagOverrideToSync(
             "test_namespace/com.android.flags.flag1", "false");
         assertTrue(flag != null);
-        assertEquals(flag.getPackageName(), "com.android.flags");
-        assertEquals(flag.getFlagName(), "flag1");
-        assertEquals("false", flag.getBootFlagValue());
-        assertEquals("false", flag.getServerFlagValue());
-        assertFalse(flag.getHasLocalOverride());
-        assertNull(flag.getLocalFlagValue());
-        assertEquals("false", flag.getDefaultFlagValue());
+        assertEquals(flag.packageName, "com.android.flags");
+        assertEquals(flag.flagName, "flag1");
+        assertEquals(flag.flagValue, "false");
+        assertEquals(flag.isLocal, false);
 
         // local override
         flag = settingsState.getFlagOverrideToSync(
             "device_config_overrides/test_namespace:com.android.flags.flag1", "false");
         assertTrue(flag != null);
-        assertEquals(flag.getPackageName(), "com.android.flags");
-        assertEquals(flag.getFlagName(), "flag1");
-        assertEquals("false", flag.getLocalFlagValue());
-        assertEquals("false", flag.getBootFlagValue());
-        assertTrue(flag.getHasLocalOverride());
-        assertNull(flag.getServerFlagValue());
-        assertEquals("false", flag.getDefaultFlagValue());
+        assertEquals(flag.packageName, "com.android.flags");
+        assertEquals(flag.flagName, "flag1");
+        assertEquals(flag.flagValue, "false");
+        assertEquals(flag.isLocal, true);
     }
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -1057,25 +1020,18 @@
                 InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
-        Map<String, AconfigdFlagInfo> flags = new HashMap<>();
-        AconfigdFlagInfo flag = AconfigdFlagInfo.newBuilder()
-        .setPackageName("com.android.flags")
-                .setFlagName("flag1")
-                .setBootFlagValue("true").build();
-        flags.put("com.android.flags/flag1", flag);
-
         synchronized (lock) {
             settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
                     "false", null, false, "aconfig");
 
             // first bulk sync
-            ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags);
+            ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
             assertTrue(requests != null);
             String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
             assertEquals("true", value);
 
             // send time should no longer bulk sync
-            requests = settingsState.handleBulkSyncToNewStorage(flags);
+            requests = settingsState.handleBulkSyncToNewStorage();
             assertTrue(requests == null);
             value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
             assertEquals("true", value);
@@ -1091,200 +1047,21 @@
                 InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
-        Map<String, AconfigdFlagInfo> flags = new HashMap<>();
         synchronized (lock) {
             settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
                     "true", null, false, "aconfig");
 
             // when aconfigd is off, should change the marker to false
-            ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags);
+            ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
             assertTrue(requests == null);
             String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
             assertEquals("false", value);
 
             // marker started with false value, after call, it should remain false
-            requests = settingsState.handleBulkSyncToNewStorage(flags);
+            requests = settingsState.handleBulkSyncToNewStorage();
             assertTrue(requests == null);
             value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
             assertEquals("false", value);
         }
     }
-
-    @Test
-    public void testGetAllAconfigFlagsFromSettings() throws Exception {
-        final Object lock = new Object();
-        final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
-        os.print(
-                "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>"
-                        + "<settings version=\"120\">"
-                        + "  <setting id=\"0\" name=\"test_namespace/com.android.flags.flag1\" "
-                            + "value=\"false\" package=\"com.android.flags\" />"
-                        + "  <setting id=\"1\" name=\"device_config_overrides/test_namespace:com.android.flags.flag1\" "
-                            + "value=\"true\" package=\"com.android.flags\" />"
-                        + "  <setting id=\"2\" name=\"device_config_overrides/test_namespace:com.android.flags.flag2\" "
-                            + "value=\"true\" package=\"com.android.flags\" />"
-                        + "  <setting id=\"3\" name=\"test_namespace/com.android.flags.flag3\" "
-                            + "value=\"true\" package=\"com.android.flags\" />"
-                        + "</settings>");
-        os.close();
-
-        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
-
-        SettingsState settingsState = new SettingsState(
-                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
-        Map<String, AconfigdFlagInfo> ret;
-        synchronized (lock) {
-            ret = settingsState.getAllAconfigFlagsFromSettings();
-        }
-
-        assertTrue(ret.isEmpty());
-
-        parsed_flags flags =
-                parsed_flags
-                        .newBuilder()
-                        .addParsedFlag(
-                                parsed_flag
-                                        .newBuilder()
-                                        .setPackage("com.android.flags")
-                                        .setName("flag1")
-                                        .setNamespace("test_namespace")
-                                        .setDescription("test flag")
-                                        .addBug("12345678")
-                                        .setState(Aconfig.flag_state.DISABLED)
-                                        .setPermission(Aconfig.flag_permission.READ_WRITE))
-                        .addParsedFlag(
-                                parsed_flag
-                                        .newBuilder()
-                                        .setPackage("com.android.flags")
-                                        .setName("flag2")
-                                        .setNamespace("test_namespace")
-                                        .setDescription("test flag")
-                                        .addBug("12345678")
-                                        .setState(Aconfig.flag_state.DISABLED)
-                                        .setPermission(Aconfig.flag_permission.READ_WRITE))
-                        .addParsedFlag(
-                                parsed_flag
-                                        .newBuilder()
-                                        .setPackage("com.android.flags")
-                                        .setName("flag3")
-                                        .setNamespace("test_namespace")
-                                        .setDescription("test flag")
-                                        .addBug("12345678")
-                                        .setState(Aconfig.flag_state.DISABLED)
-                                        .setPermission(Aconfig.flag_permission.READ_WRITE))
-                        .build();
-
-        Map<String, Map<String, String>> defaults = new HashMap<>();
-        Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
-        synchronized (lock) {
-            settingsState.loadAconfigDefaultValues(
-                flags.toByteArray(), defaults, flagInfoDefault);
-            settingsState.addAconfigDefaultValuesFromMap(defaults);
-            ret = settingsState.getAllAconfigFlagsFromSettings();
-        }
-
-        AconfigdFlagInfo expectedFlag1 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag1")
-                        .setServerFlagValue("false")
-                        .setLocalFlagValue("true")
-                        .setDefaultFlagValue("false")
-                        .setBootFlagValue("true")
-                        .setHasServerOverride(true)
-                        .setHasLocalOverride(true)
-                        .setIsReadWrite(false)
-                        .build();
-
-        AconfigdFlagInfo expectedFlag2 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag2")
-                        .setLocalFlagValue("true")
-                        .setDefaultFlagValue("false")
-                        .setBootFlagValue("true")
-                        .setHasLocalOverride(true)
-                        .setHasServerOverride(false)
-                        .setIsReadWrite(false)
-                        .build();
-
-
-        AconfigdFlagInfo expectedFlag3 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag3")
-                        .setServerFlagValue("true")
-                        .setBootFlagValue("true")
-                        .setDefaultFlagValue("false")
-                        .setHasServerOverride(true)
-                        .setIsReadWrite(false)
-                        .build();
-
-        assertEquals(expectedFlag1, ret.get("com.android.flags.flag1"));
-        assertEquals(expectedFlag2, ret.get("com.android.flags.flag2"));
-        assertEquals(expectedFlag3, ret.get("com.android.flags.flag3"));
-    }
-
-    @Test
-    public void testCompareFlagValueInNewStorage() {
-                int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
-        Object lock = new Object();
-        SettingsState settingsState =
-                new SettingsState(
-                        InstrumentationRegistry.getContext(),
-                        lock,
-                        mSettingsFile,
-                        configKey,
-                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED,
-                        Looper.getMainLooper());
-
-        AconfigdFlagInfo defaultFlag1 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag1")
-                        .setDefaultFlagValue("false")
-                        .build();
-
-        AconfigdFlagInfo settingFlag1 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag1")
-                        .setServerFlagValue("true")
-                        .setHasServerOverride(true)
-                        .build();
-
-        AconfigdFlagInfo expectedFlag1 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag1")
-                        .setBootFlagValue("true")
-                        .setServerFlagValue("true")
-                        .setDefaultFlagValue("false")
-                        .setHasServerOverride(true)
-                        .build();
-
-        Map<String, AconfigdFlagInfo> settingMap = new HashMap<>();
-        Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>();
-        Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>();
-
-        defaultMap.put("com.android.flags.flag1", defaultFlag1);
-        settingMap.put("com.android.flags.flag1", settingFlag1);
-        aconfigdMap.put("com.android.flags.flag1", expectedFlag1);
-
-        int ret = settingsState.compareFlagValueInNewStorage(settingMap, defaultMap, aconfigdMap);
-        assertEquals(0, ret);
-
-        AconfigdFlagInfo defaultFlag2 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag2")
-                        .setDefaultFlagValue("false")
-                        .build();
-        defaultMap.put("com.android.flags.flag2", defaultFlag2);
-
-        ret = settingsState.compareFlagValueInNewStorage(settingMap, defaultMap, aconfigdMap);
-        assertEquals(1, ret);
-    }
 }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7ce8f98..63a52d6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -177,16 +177,9 @@
 }
 
 flag {
-    name: "notification_throttle_hun"
-    namespace: "systemui"
-    description: "During notification avalanche, throttle HUNs showing in fast succession."
-    bug: "307288824"
-}
-
-flag {
     name: "notification_avalanche_throttle_hun"
     namespace: "systemui"
-    description: "(currently unused) During notification avalanche, throttle HUNs showing in fast succession."
+    description: "During notification avalanche, throttle HUNs showing in fast succession."
     bug: "307288824"
 }
 
@@ -997,6 +990,13 @@
 }
 
 flag {
+  name: "glanceable_hub_fullscreen_swipe"
+  namespace: "systemui"
+  description: "Increase swipe area for gestures to bring in glanceable hub"
+  bug: "339665673"
+}
+
+flag {
   name: "glanceable_hub_shortcut_button"
   namespace: "systemui"
   description: "Shows a button over the dream and lock screen to open the glanceable hub"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
deleted file mode 100644
index dff8753..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.compose.ui.platform
-
-import android.content.Context
-import android.content.res.Configuration
-import android.util.AttributeSet
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.platform.AbstractComposeView
-
-/**
- * A ComposeView that recreates its composition if the display size or font scale was changed.
- *
- * TODO(b/317317814): Remove this workaround.
- */
-class DensityAwareComposeView(context: Context) : OpenComposeView(context) {
-    private var lastDensityDpi: Int = -1
-    private var lastFontScale: Float = -1f
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-
-        val configuration = context.resources.configuration
-        lastDensityDpi = configuration.densityDpi
-        lastFontScale = configuration.fontScale
-    }
-
-    override fun dispatchConfigurationChanged(newConfig: Configuration) {
-        super.dispatchConfigurationChanged(newConfig)
-
-        // If the density or font scale changed, we dispose then recreate the composition. Note that
-        // we do this here after dispatching the new configuration to children (instead of doing
-        // this in onConfigurationChanged()) because the new configuration should first be
-        // dispatched to the AndroidComposeView that holds the current density before we recreate
-        // the composition.
-        val densityDpi = newConfig.densityDpi
-        val fontScale = newConfig.fontScale
-        if (densityDpi != lastDensityDpi || fontScale != lastFontScale) {
-            lastDensityDpi = densityDpi
-            lastFontScale = fontScale
-
-            disposeComposition()
-            if (isAttachedToWindow) {
-                createComposition()
-            }
-        }
-    }
-}
-
-/** A fork of [androidx.compose.ui.platform.ComposeView] that is open and can be subclassed. */
-open class OpenComposeView
-internal constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
-    AbstractComposeView(context, attrs, defStyleAttr) {
-
-    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
-
-    @Suppress("RedundantVisibilityModifier")
-    protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
-
-    @Composable
-    override fun Content() {
-        content.value?.invoke()
-    }
-
-    override fun getAccessibilityClassName(): CharSequence {
-        return javaClass.name
-    }
-
-    /**
-     * Set the Jetpack Compose UI content for this view. Initial composition will occur when the
-     * view becomes attached to a window or when [createComposition] is called, whichever comes
-     * first.
-     */
-    fun setContent(content: @Composable () -> Unit) {
-        shouldCreateCompositionOnAttachedToWindow = true
-        this.content.value = content
-        if (isAttachedToWindow) {
-            createComposition()
-        }
-    }
-}
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 feb1f5b..a90f82e 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
@@ -21,6 +21,8 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.CommunalSwipeDetector
+import com.android.compose.animation.scene.DefaultSwipeDetector
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.ElementMatcher
@@ -35,6 +37,7 @@
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.Flags
+import com.android.systemui.Flags.glanceableHubFullscreenSwipe
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -108,6 +111,8 @@
         )
     }
 
+    val detector = remember { CommunalSwipeDetector() }
+
     DisposableEffect(state) {
         val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
         dataSourceDelegator.setDelegate(dataSource)
@@ -121,13 +126,25 @@
         onDispose { viewModel.setTransitionState(null) }
     }
 
+    val swipeSourceDetector =
+        if (glanceableHubFullscreenSwipe()) {
+            detector
+        } else {
+            FixedSizeEdgeDetector(dimensionResource(id = R.dimen.communal_gesture_initiation_width))
+        }
+
+    val swipeDetector =
+        if (glanceableHubFullscreenSwipe()) {
+            detector
+        } else {
+            DefaultSwipeDetector
+        }
+
     SceneTransitionLayout(
         state = state,
         modifier = modifier.fillMaxSize(),
-        swipeSourceDetector =
-            FixedSizeEdgeDetector(
-                dimensionResource(id = R.dimen.communal_gesture_initiation_width)
-            ),
+        swipeSourceDetector = swipeSourceDetector,
+        swipeDetector = swipeDetector,
     ) {
         scene(
             CommunalScenes.Blank,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9dd3d39..1f7f07b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -28,7 +28,9 @@
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
@@ -445,6 +447,14 @@
                 val selected by
                     remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
                 DraggableItem(
+                    modifier =
+                        if (dragDropState.draggingItemIndex == index) {
+                            Modifier
+                        } else {
+                            Modifier.animateItem(
+                                placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
+                            )
+                        },
                     dragDropState = dragDropState,
                     selected = selected,
                     enabled = list[index].isWidgetContent(),
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 c26259f..ee3ffce 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
@@ -126,7 +126,8 @@
                             " size=${coordinates.size}" +
                             " bounds=$boundsInWindow"
                     }
-                    viewModel.onHeadsUpTopChanged(boundsInWindow.top)
+                    // Note: boundsInWindow doesn't scroll off the screen
+                    stackScrollView.setHeadsUpTop(boundsInWindow.top)
                 }
     ) {
         content {}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index ae53d56..7ca68fb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -18,8 +18,8 @@
 
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
@@ -75,7 +75,7 @@
         OverlayShade(
             modifier = modifier,
             viewModel = overlayShadeViewModel,
-            horizontalArrangement = Arrangement.Start,
+            horizontalArrangement = Arrangement.End,
             lockscreenContent = lockscreenContent,
         ) {
             Column {
@@ -95,7 +95,7 @@
                     shouldPunchHoleBehindScrim = false,
                     shouldFillMaxSize = false,
                     shadeMode = ShadeMode.Dual,
-                    modifier = Modifier.width(416.dp),
+                    modifier = Modifier.fillMaxWidth(),
                 )
             }
         }
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 975829a..efda4cd 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,17 +17,28 @@
 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.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.qs.ui.composable.QuickSettings
+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
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.StateFlow
 
@@ -39,6 +50,8 @@
 class GoneScene
 @Inject
 constructor(
+    private val notificationStackScrolLView: Lazy<NotificationScrollView>,
+    private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     private val viewModel: GoneSceneViewModel,
 ) : ComposableScene {
     override val key = Scenes.Gone
@@ -55,5 +68,28 @@
             key = QuickSettings.SharedValues.TilesSquishiness,
         )
         Spacer(modifier.fillMaxSize())
+        HeadsUpNotificationStack(
+            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/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index f5a0ef2..3255b08 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.shade.ui.composable.Shade
 
 /**
@@ -102,4 +103,10 @@
             y = { Shade.Dimensions.ScrimOverscrollLimit }
         )
     }
+    overscroll(Scenes.NotificationsShade, Orientation.Vertical) {
+        translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit })
+    }
+    overscroll(Scenes.QuickSettingsShade, Orientation.Vertical) {
+        translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit })
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index a6b268d..6b3b760 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -50,8 +50,7 @@
             }
         }
 
-    translate(OverlayShade.Elements.PanelBackground, Edge.Top)
-    translate(Notifications.Elements.NotificationScrim, Edge.Top)
+    translate(OverlayShade.Elements.Panel, Edge.Top)
 
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index 2baaecf..ec2f14f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -48,7 +48,7 @@
             }
         }
 
-    translate(OverlayShade.Elements.PanelBackground, Edge.Top)
+    translate(OverlayShade.Elements.Panel, Edge.Top)
 
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 34cc676..a730206 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -79,7 +79,10 @@
                 },
             horizontalArrangement = horizontalArrangement,
         ) {
-            Panel(modifier = Modifier.panelSize(), content = content)
+            Panel(
+                modifier = Modifier.element(OverlayShade.Elements.Panel).panelSize(),
+                content = content
+            )
         }
     }
 }
@@ -138,6 +141,7 @@
 object OverlayShade {
     object Elements {
         val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
+        val Panel = ElementKey("OverlayShadePanel", scenePicker = LowestZIndexScenePicker)
         val PanelBackground =
             ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker)
     }
@@ -153,6 +157,7 @@
         val PanelCornerRadius = 46.dp
         val PanelWidthMedium = 390.dp
         val PanelWidthLarge = 474.dp
+        val OverscrollLimit = 100f
     }
 
     object Shapes {
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 271eb96..fbf91b7 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
@@ -68,9 +68,7 @@
                     state.a11yClickDescription?.let {
                         customActions =
                             listOf(
-                                CustomAccessibilityAction(
-                                    it,
-                                ) {
+                                CustomAccessibilityAction(it) {
                                     onIconTapped()
                                     true
                                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
index ac5004e..580aba5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.ui.composable
 
+import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
@@ -56,17 +57,19 @@
                     with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
                 }
             }
-            Row(
-                modifier = Modifier.fillMaxWidth(),
-                horizontalArrangement = Arrangement.spacedBy(spacing),
-            ) {
-                for (component in layout.footerComponents) {
-                    AnimatedVisibility(
-                        visible = component.isVisible,
-                        modifier = Modifier.weight(1f),
-                    ) {
-                        with(component.component as ComposeVolumePanelUiComponent) {
-                            Content(Modifier)
+            AnimatedContent(
+                targetState = layout.footerComponents,
+                label = "FooterComponentAnimation",
+            ) { footerComponents ->
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.spacedBy(spacing),
+                ) {
+                    for (component in footerComponents) {
+                        if (component.isVisible) {
+                            with(component.component as ComposeVolumePanelUiComponent) {
+                                Content(Modifier.weight(1f))
+                            }
                         }
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index 9ea20b9..6349c14 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.ui.composable
 
+import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
@@ -50,26 +51,27 @@
                 with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
             }
         }
-        if (layout.footerComponents.isNotEmpty()) {
+
+        AnimatedContent(
+            targetState = layout.footerComponents,
+            label = "FooterComponentAnimation",
+        ) { footerComponents ->
             Row(
                 modifier = Modifier.fillMaxWidth().wrapContentHeight(),
                 horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
             ) {
                 val visibleComponentsCount =
-                    layout.footerComponents.fastSumBy { if (it.isVisible) 1 else 0 }
+                    footerComponents.fastSumBy { if (it.isVisible) 1 else 0 }
 
                 // Center footer component if there is only one present
                 if (visibleComponentsCount == 1) {
                     Spacer(modifier = Modifier.weight(0.5f))
                 }
 
-                for (component in layout.footerComponents) {
-                    AnimatedVisibility(
-                        visible = component.isVisible,
-                        modifier = Modifier.weight(1f),
-                    ) {
+                for (component in footerComponents) {
+                    if (component.isVisible) {
                         with(component.component as ComposeVolumePanelUiComponent) {
-                            Content(Modifier)
+                            Content(Modifier.weight(1f))
                         }
                     }
                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt
new file mode 100644
index 0000000..7be34ca
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.abs
+
+private const val TRAVEL_RATIO_THRESHOLD = .5f
+
+/**
+ * {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link
+ * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable
+ * hub.
+ */
+class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) :
+    SwipeSourceDetector, SwipeDetector {
+    override fun source(
+        layoutSize: IntSize,
+        position: IntOffset,
+        density: Density,
+        orientation: Orientation
+    ): SwipeSource? {
+        return lastDirection
+    }
+
+    override fun detectSwipe(change: PointerInputChange): Boolean {
+        if (change.positionChange().x > 0) {
+            lastDirection = Edge.Left
+        } else {
+            lastDirection = Edge.Right
+        }
+
+        // Determine whether the ratio of the distance traveled horizontally to the distance
+        // traveled vertically exceeds the threshold.
+        return abs(change.positionChange().x / change.positionChange().y) > TRAVEL_RATIO_THRESHOLD
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 0fc0053..3cc8431 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -72,6 +72,7 @@
     enabled: () -> Boolean,
     startDragImmediately: (startedPosition: Offset) -> Boolean,
     onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    swipeDetector: SwipeDetector = DefaultSwipeDetector,
 ): Modifier =
     this.then(
         MultiPointerDraggableElement(
@@ -79,6 +80,7 @@
             enabled,
             startDragImmediately,
             onDragStarted,
+            swipeDetector,
         )
     )
 
@@ -88,6 +90,7 @@
     private val startDragImmediately: (startedPosition: Offset) -> Boolean,
     private val onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    private val swipeDetector: SwipeDetector,
 ) : ModifierNodeElement<MultiPointerDraggableNode>() {
     override fun create(): MultiPointerDraggableNode =
         MultiPointerDraggableNode(
@@ -95,6 +98,7 @@
             enabled = enabled,
             startDragImmediately = startDragImmediately,
             onDragStarted = onDragStarted,
+            swipeDetector = swipeDetector,
         )
 
     override fun update(node: MultiPointerDraggableNode) {
@@ -102,6 +106,7 @@
         node.enabled = enabled
         node.startDragImmediately = startDragImmediately
         node.onDragStarted = onDragStarted
+        node.swipeDetector = swipeDetector
     }
 }
 
@@ -111,6 +116,7 @@
     var startDragImmediately: (startedPosition: Offset) -> Boolean,
     var onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    var swipeDetector: SwipeDetector = DefaultSwipeDetector,
 ) :
     PointerInputModifierNode,
     DelegatingNode(),
@@ -199,6 +205,7 @@
                             onDragCancel = { controller ->
                                 controller.onStop(velocity = 0f, canChangeScene = true)
                             },
+                            swipeDetector = swipeDetector
                         )
                     } catch (exception: CancellationException) {
                         // If the coroutine scope is active, we can just restart the drag cycle.
@@ -226,7 +233,8 @@
             (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
         onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
         onDragEnd: (controller: DragController) -> Unit,
-        onDragCancel: (controller: DragController) -> Unit
+        onDragCancel: (controller: DragController) -> Unit,
+        swipeDetector: SwipeDetector,
     ) {
         // Wait for a consumable event in [PointerEventPass.Main] pass
         val consumablePointer = awaitConsumableEvent().changes.first()
@@ -238,8 +246,10 @@
                 consumablePointer
             } else {
                 val onSlopReached = { change: PointerInputChange, over: Float ->
-                    change.consume()
-                    overSlop = over
+                    if (swipeDetector.detectSwipe(change)) {
+                        change.consume()
+                        overSlop = over
+                    }
                 }
 
                 // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 11e711a..cf8c584 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -55,6 +55,7 @@
     state: SceneTransitionLayoutState,
     modifier: Modifier = Modifier,
     swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+    swipeDetector: SwipeDetector = DefaultSwipeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
@@ -62,6 +63,7 @@
         state,
         modifier,
         swipeSourceDetector,
+        swipeDetector,
         transitionInterceptionThreshold,
         onLayoutImpl = null,
         scenes,
@@ -95,6 +97,7 @@
     transitions: SceneTransitions,
     modifier: Modifier = Modifier,
     swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+    swipeDetector: SwipeDetector = DefaultSwipeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
     scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -111,6 +114,7 @@
         state,
         modifier,
         swipeSourceDetector,
+        swipeDetector,
         transitionInterceptionThreshold,
         scenes,
     )
@@ -467,6 +471,7 @@
     state: SceneTransitionLayoutState,
     modifier: Modifier = Modifier,
     swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+    swipeDetector: SwipeDetector = DefaultSwipeDetector,
     transitionInterceptionThreshold: Float = 0f,
     onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
     scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -502,5 +507,5 @@
         layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
     }
 
-    layoutImpl.Content(modifier)
+    layoutImpl.Content(modifier, swipeDetector)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 7856498..c614265 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -185,14 +185,14 @@
     }
 
     @Composable
-    internal fun Content(modifier: Modifier) {
+    internal fun Content(modifier: Modifier, swipeDetector: SwipeDetector) {
         Box(
             modifier
                 // Handle horizontal and vertical swipes on this layout.
                 // Note: order here is important and will give a slight priority to the vertical
                 // swipes.
-                .swipeToScene(horizontalDraggableHandler)
-                .swipeToScene(verticalDraggableHandler)
+                .swipeToScene(horizontalDraggableHandler, swipeDetector)
+                .swipeToScene(verticalDraggableHandler, swipeDetector)
                 .then(LayoutElement(layoutImpl = this))
         ) {
             LookaheadScope {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
new file mode 100644
index 0000000..54ee783
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.input.pointer.PointerInputChange
+
+/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */
+@Stable
+interface SwipeDetector {
+    /**
+     * Invoked on changes to pointer input. Returns {@code true} if a swipe has been recognized,
+     * {@code false} otherwise.
+     */
+    fun detectSwipe(change: PointerInputChange): Boolean
+}
+
+val DefaultSwipeDetector = PassthroughSwipeDetector()
+
+/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */
+class PassthroughSwipeDetector : SwipeDetector {
+    override fun detectSwipe(change: PointerInputChange): Boolean {
+        // Simply accept all changes as a swipe
+        return true
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index b618369..171e243 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -31,14 +31,18 @@
  * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
  */
 @Stable
-internal fun Modifier.swipeToScene(draggableHandler: DraggableHandlerImpl): Modifier {
-    return this.then(SwipeToSceneElement(draggableHandler))
+internal fun Modifier.swipeToScene(
+    draggableHandler: DraggableHandlerImpl,
+    swipeDetector: SwipeDetector
+): Modifier {
+    return this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
 }
 
 private data class SwipeToSceneElement(
     val draggableHandler: DraggableHandlerImpl,
+    val swipeDetector: SwipeDetector
 ) : ModifierNodeElement<SwipeToSceneNode>() {
-    override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler)
+    override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler, swipeDetector)
 
     override fun update(node: SwipeToSceneNode) {
         node.draggableHandler = draggableHandler
@@ -47,6 +51,7 @@
 
 private class SwipeToSceneNode(
     draggableHandler: DraggableHandlerImpl,
+    swipeDetector: SwipeDetector,
 ) : DelegatingNode(), PointerInputModifierNode {
     private val delegate =
         delegate(
@@ -55,6 +60,7 @@
                 enabled = ::enabled,
                 startDragImmediately = ::startDragImmediately,
                 onDragStarted = draggableHandler::onDragStarted,
+                swipeDetector = swipeDetector,
             )
         )
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index aa6d113..4bb643f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalViewConfiguration
@@ -346,4 +347,69 @@
         continueDraggingDown()
         assertThat(stopped).isTrue()
     }
+
+    @Test
+    fun multiPointerSwipeDetectorInteraction() {
+        val size = 200f
+        val middle = Offset(size / 2f, size / 2f)
+
+        var started = false
+
+        var capturedChange: PointerInputChange? = null
+        var swipeConsume = false
+
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { true },
+                        startDragImmediately = { false },
+                        swipeDetector =
+                            object : SwipeDetector {
+                                override fun detectSwipe(change: PointerInputChange): Boolean {
+                                    capturedChange = change
+                                    return swipeConsume
+                                }
+                            },
+                        onDragStarted = { _, _, _ ->
+                            started = true
+                            object : DragController {
+                                override fun onDrag(delta: Float) {}
+
+                                override fun onStop(velocity: Float, canChangeScene: Boolean) {}
+                            }
+                        },
+                    )
+            ) {}
+        }
+
+        fun startDraggingDown() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(0f, touchSlop))
+            }
+        }
+
+        fun continueDraggingDown() {
+            rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+        }
+
+        startDraggingDown()
+        assertThat(capturedChange).isNotNull()
+        capturedChange = null
+        assertThat(started).isFalse()
+
+        swipeConsume = true
+        continueDraggingDown()
+        assertThat(capturedChange).isNotNull()
+        capturedChange = null
+
+        continueDraggingDown()
+        assertThat(capturedChange).isNull()
+
+        assertThat(started).isTrue()
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
index 165e972..de9baa5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
+++ b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
@@ -79,6 +79,21 @@
         }
     }
 
+    /**
+     * Asserts that the current thread is the same as the given thread, or that the current thread
+     * is the test thread.
+     * @param expected The looper we expected to be running on
+     */
+    public static void isCurrentThread(Looper expected) {
+        if (!expected.isCurrentThread()
+                && (sTestThread == null || sTestThread != Thread.currentThread())) {
+            throw new IllegalStateException("Called on wrong thread thread."
+                    + " wanted " + expected.getThread().getName()
+                    + " but instead got Thread.currentThread()="
+                    + Thread.currentThread().getName());
+        }
+    }
+
     public static void isNotMainThread() {
         if (sMainLooper.isCurrentThread()
                 && (sTestThread == null || sTestThread == Thread.currentThread())) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
index e39ad4f..a676c7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -25,15 +25,18 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,6 +77,8 @@
             ScreenBrightnessDisplayManagerRepository(
                 displayId,
                 displayManager,
+                FakeLogBuffer.Factory.create(),
+                mock<TableLogBuffer>(),
                 kosmos.applicationCoroutineScope,
                 kosmos.testDispatcher,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
index 33c44f8..b6616bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
@@ -20,13 +20,16 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.display.BrightnessUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.brightness.data.model.LinearBrightness
 import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
 import com.android.systemui.brightness.data.repository.screenBrightnessRepository
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -41,7 +44,14 @@
 
     private val kosmos = testKosmos()
 
-    private val underTest = ScreenBrightnessInteractor(kosmos.screenBrightnessRepository)
+    private val underTest =
+        with(kosmos) {
+            ScreenBrightnessInteractor(
+                screenBrightnessRepository,
+                applicationCoroutineScope,
+                mock<TableLogBuffer>()
+            )
+        }
 
     @Test
     fun gammaBrightness() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
index 0058ee4..8402676 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -20,15 +20,16 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.display.BrightnessUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.brightness.data.model.LinearBrightness
 import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
 import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
 import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
@@ -52,6 +53,7 @@
             BrightnessSliderViewModel(
                 screenBrightnessInteractor,
                 brightnessPolicyEnforcementInteractor,
+                applicationCoroutineScope,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 723f6a2..9300db9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -16,29 +16,40 @@
 package com.android.systemui.dreams.homecontrols
 
 import android.app.Activity
+import android.content.Intent
+import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL
+import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM
+import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE
+import android.window.TaskFragmentInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class HomeControlsDreamServiceTest : SysuiTestCase() {
@@ -46,31 +57,38 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
-    private lateinit var fakeWakeLock: WakeLockFake
+    private val fakeWakeLock = WakeLockFake()
+    private val fakeWakeLockBuilder by lazy {
+        WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) }
+    }
 
-    @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
-    @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
-    @Mock private lateinit var activity: Activity
+    private val taskFragmentComponent = mock<TaskFragmentComponent>()
+    private val activity = mock<Activity>()
+    private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
+    private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
+    private val hideCallback = argumentCaptor<() -> Unit>()
+    private val dreamServiceDelegate =
+        mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity }
 
-    private lateinit var underTest: HomeControlsDreamService
+    private val taskFragmentComponentFactory =
+        mock<TaskFragmentComponent.Factory> {
+            on {
+                create(
+                    activity = eq(activity),
+                    onCreateCallback = onCreateCallback.capture(),
+                    onInfoChangedCallback = onInfoChangedCallback.capture(),
+                    hide = hideCallback.capture(),
+                )
+            } doReturn taskFragmentComponent
+        }
+
+    private val underTest: HomeControlsDreamService by lazy { buildService() }
 
     @Before
-    fun setup() =
-        with(kosmos) {
-            MockitoAnnotations.initMocks(this@HomeControlsDreamServiceTest)
-            whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
-                .thenReturn(taskFragmentComponent)
-
-            fakeWakeLock = WakeLockFake()
-            fakeWakeLockBuilder = WakeLockFake.Builder(context)
-            fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
-
-            whenever(controlsComponent.getControlsListingController())
-                .thenReturn(Optional.of(controlsListingController))
-
-            underTest = buildService { activity }
-        }
+    fun setup() {
+        whenever(kosmos.controlsComponent.getControlsListingController())
+            .thenReturn(Optional.of(kosmos.controlsListingController))
+    }
 
     @Test
     fun testOnAttachedToWindowCreatesTaskFragmentComponent() =
@@ -90,9 +108,12 @@
     @Test
     fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() =
         testScope.runTest {
-            underTest = buildService { null }
+            val serviceWithNullActivity =
+                buildService(
+                    mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null }
+                )
 
-            underTest.onAttachedToWindow()
+            serviceWithNullActivity.onAttachedToWindow()
             verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
         }
 
@@ -102,6 +123,7 @@
             underTest.onAttachedToWindow()
             assertThat(fakeWakeLock.isHeld).isTrue()
         }
+
     @Test
     fun testDetachWindow_wakeLockCanBeReleased() =
         testScope.runTest {
@@ -112,14 +134,60 @@
             assertThat(fakeWakeLock.isHeld).isFalse()
         }
 
-    private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
+    @Test
+    fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() =
+        testScope.runTest {
+            whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false)
+            underTest.onAttachedToWindow()
+            onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+            verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
+
+            // Task fragment becomes empty
+            onInfoChangedCallback.firstValue.invoke(
+                mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
+            )
+            advanceUntilIdle()
+            // Dream is finished and activity is not restarted
+            verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
+            verify(dreamServiceDelegate, never()).wakeUp(any())
+            verify(dreamServiceDelegate).finish(any())
+        }
+
+    @Test
+    fun testRestartsActivityWhenRedirectingWakes() =
+        testScope.runTest {
+            whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true)
+            underTest.onAttachedToWindow()
+            onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+            verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
+
+            // Task fragment becomes empty
+            onInfoChangedCallback.firstValue.invoke(
+                mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
+            )
+            advanceUntilIdle()
+            // Activity is restarted instead of finishing the dream.
+            verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher())
+            verify(dreamServiceDelegate).wakeUp(any())
+            verify(dreamServiceDelegate, never()).finish(any())
+        }
+
+    private fun intentMatcher() =
+        argThat<Intent> {
+            getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) ==
+                CONTROLS_SURFACE_DREAM
+        }
+
+    private fun buildService(
+        activityProvider: DreamServiceDelegate = dreamServiceDelegate
+    ): HomeControlsDreamService =
         with(kosmos) {
             return HomeControlsDreamService(
                 controlsSettingsRepository = FakeControlsSettingsRepository(),
                 taskFragmentFactory = taskFragmentComponentFactory,
                 homeControlsComponentInteractor = homeControlsComponentInteractor,
-                fakeWakeLockBuilder,
-                dreamActivityProvider = activityProvider,
+                wakeLockBuilder = fakeWakeLockBuilder,
+                dreamServiceDelegate = activityProvider,
                 bgDispatcher = testDispatcher,
                 logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index d630a2f..6c5001a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -136,20 +136,20 @@
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissibleKeyguard() =
         testScope.runTest {
             powerInteractor.onCameraLaunchGestureDetected()
             powerInteractor.setAwakeForTest()
             advanceTimeBy(100) // account for debouncing
 
-            // We should head back to GONE since we started there.
+            // We should head to OCCLUDED because keyguard is not dismissible.
             assertThat(transitionRepository)
                 .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
         }
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+    fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissibleKeyguard() =
         testScope.runTest {
             kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
             powerInteractor.onCameraLaunchGestureDetected()
@@ -188,6 +188,7 @@
             )
 
             // Detect a power gesture and then wake up.
+            kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
             reset(transitionRepository)
             powerInteractor.onCameraLaunchGestureDetected()
             powerInteractor.setAwakeForTest()
@@ -355,6 +356,7 @@
             )
 
             // Detect a power gesture and then wake up.
+            kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
             reset(transitionRepository)
             powerInteractor.onCameraLaunchGestureDetected()
             powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index bfc7775..612f2e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -56,13 +56,13 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.testKosmos
 import junit.framework.Assert.assertEquals
-import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
@@ -230,6 +230,7 @@
             )
 
             // Detect a power gesture and then wake up.
+            kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
             reset(transitionRepository)
             powerInteractor.onCameraLaunchGestureDetected()
             powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 78a1167..5068f68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -350,7 +350,7 @@
         }
 
     @Test
-    fun quickAffordance_updateOncePerShadeExpansion() =
+    fun quickAffordance_doNotSendUpdatesWhileShadeExpandingAndStillHidden() =
         testScope.runTest {
             val shadeExpansion = MutableStateFlow(0f)
             whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
@@ -365,7 +365,9 @@
                 shadeExpansion.value = i / 10f
             }
 
-            assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+            assertThat(collectedValue[0])
+                .isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+            assertThat(collectedValue.size).isEqualTo(initialSize)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index 04c270d..ad24a71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.kosmos.testScope
@@ -104,6 +105,7 @@
                 burnInInteractor = burnInInteractor,
                 shortcutsCombinedViewModel = shortcutsCombinedViewModel,
                 configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+                keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 37d4721..7ebebd7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -203,6 +203,21 @@
                 .containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles)
         }
 
+    @Test
+    fun prependDefault_noChangesWhenInRetail() =
+        testScope.runTest {
+            val user = 0
+            retailModeRepository.setRetailMode(true)
+            val startingTiles = "a"
+            storeTilesForUser(startingTiles, user)
+
+            runCurrent()
+            underTest.prependDefault(user)
+            runCurrent()
+
+            assertThat(loadTilesForUser(user)).isEqualTo(startingTiles)
+        }
+
     private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
         secureSettings.putStringForUser(SETTING, specs, forUser)
         runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index 58fc109..b12fbc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -327,6 +327,32 @@
             assertThat(loadTiles()).isEqualTo(expected)
         }
 
+    @Test
+    fun setTilesWithRepeats_onlyDistinctTiles() =
+        testScope.runTest {
+            val tilesToSet = "a,b,c,a,d,b".toTileSpecs()
+            val expected = "a,b,c,d"
+
+            val tiles by collectLastValue(underTest.tiles())
+            underTest.setTiles(tilesToSet)
+
+            assertThat(tiles).isEqualTo(expected.toTileSpecs())
+            assertThat(loadTiles()).isEqualTo(expected)
+        }
+
+    @Test
+    fun prependDefaultTwice_doesntAddMoreTiles() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles())
+            underTest.setTiles(listOf(TileSpec.create("a")))
+
+            underTest.prependDefault()
+            val currentTiles = tiles!!
+            underTest.prependDefault()
+
+            assertThat(tiles).isEqualTo(currentTiles)
+        }
+
     private fun getDefaultTileSpecs(): List<TileSpec> {
         return defaultTilesRepository.defaultTiles
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 1c73fe2b..6ad4b31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.qs.toProto
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.any
@@ -85,6 +86,7 @@
     private val pipelineFlags = QSPipelineFlagsRepository()
     private val tileLifecycleManagerFactory = TLMFactory()
     private val minimumTilesRepository = MinimumTilesFixedRepository()
+    private val retailModeRepository = FakeRetailModeRepository()
 
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
 
@@ -118,6 +120,7 @@
                 installedTilesComponentRepository = installedTilesPackageRepository,
                 userRepository = userRepository,
                 minimumTilesRepository = minimumTilesRepository,
+                retailModeRepository = retailModeRepository,
                 customTileStatePersister = customTileStatePersister,
                 tileFactory = tileFactory,
                 newQSTileFactory = { newQSTileFactory },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
index 260189d..e8ad038 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeRetailModeRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.qsTileFactory
 import com.android.systemui.settings.fakeUserTracker
@@ -138,6 +140,19 @@
             }
         }
 
+    @Test
+    fun inRetailMode_onlyOneTile_noPrependDefault() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeRetailModeRepository.setRetailMode(true)
+                fakeTileSpecRepository.setTiles(0, listOf(goodTile))
+                val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+                runCurrent()
+
+                assertThat(tiles!!.map { it.spec }).isEqualTo(listOf(goodTile))
+            }
+        }
+
     private fun tileCreator(spec: String): QSTile? {
         return if (spec.contains("OEM")) {
             null // We don't know how to create OEM spec tiles
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 63f19fb..6b5d072 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -78,7 +78,7 @@
 
         // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
         // declaration, where mocks are null
-        mAvalancheController = AvalancheController(dumpManager)
+        mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake)
 
         testableHeadsUpManager =
             TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 3bfc046..88bef91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -38,6 +38,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.testing.TestableLooper;
@@ -147,7 +148,7 @@
     @Override
     public void SysuiSetup() throws Exception {
         super.SysuiSetup();
-        mAvalancheController = new AvalancheController(dumpManager);
+        mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake);
     }
 
     @Test
@@ -610,7 +611,31 @@
     }
 
     @Test
-    public void testPinEntry_logsPeek() {
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    public void testPinEntry_logsPeek_throttleEnabled() {
+        final BaseHeadsUpManager hum = createHeadsUpManager();
+
+        // Needs full screen intent in order to be pinned
+        final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
+
+        // Note: the standard way to show a notification would be calling showNotification rather
+        // than onAlertEntryAdded. However, in practice showNotification in effect adds
+        // the notification and then updates it; in order to not log twice, the entry needs
+        // to have a functional ExpandableNotificationRow that can keep track of whether it's
+        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+        hum.onEntryAdded(entryToPin);
+
+        assertEquals(2, mUiEventLoggerFake.numLogs());
+        assertEquals(AvalancheController.ThrottleEvent.SHOWN.getId(),
+                mUiEventLoggerFake.eventId(0));
+        assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
+                mUiEventLoggerFake.eventId(1));
+    }
+
+    @Test
+    @DisableFlags(NotificationThrottleHun.FLAG_NAME)
+    public void testPinEntry_logsPeek_throttleDisabled() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
 
         // Needs full screen intent in order to be pinned
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 9feb914..200e92e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -167,7 +167,7 @@
         mContext.getOrCreateTestableResources().addOverride(
                 R.integer.ambient_notification_extension_time, 500);
 
-        mAvalancheController = new AvalancheController(dumpManager);
+        mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt
new file mode 100644
index 0000000..7934b02
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.ui.navigation
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class VolumeNavigatorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val underTest: VolumeNavigator =
+        with(kosmos) {
+            VolumeNavigator(
+                testScope.backgroundScope,
+                testDispatcher,
+                mock {},
+                activityStarter,
+                volumePanelViewModelFactory,
+                mock {
+                    on { create(any(), anyInt(), anyBoolean(), any()) }.thenReturn(mock {})
+                    on { applicationContext }.thenReturn(context)
+                },
+                uiEventLoggerFake,
+                volumePanelGlobalStateInteractor,
+            )
+        }
+
+    @Test
+    fun showNewVolumePanel_keyguardLocked_notShown() =
+        with(kosmos) {
+            testScope.runTest {
+                val panelState by collectLastValue(volumePanelGlobalStateInteractor.globalState)
+
+                underTest.openVolumePanel(VolumePanelRoute.COMPOSE_VOLUME_PANEL)
+                runCurrent()
+
+                assertThat(panelState!!.isVisible).isFalse()
+            }
+        }
+
+    @Test
+    fun showNewVolumePanel_keyguardUnlocked_shown() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(activityStarter.dismissKeyguardThenExecute(any(), any(), anyBoolean()))
+                    .then { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() }
+                val panelState by collectLastValue(volumePanelGlobalStateInteractor.globalState)
+
+                underTest.openVolumePanel(VolumePanelRoute.COMPOSE_VOLUME_PANEL)
+                runCurrent()
+
+                assertThat(panelState!!.isVisible).isTrue()
+            }
+        }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 3244eb4..bf58eee 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -94,6 +94,9 @@
     default void setHasNotifications(boolean hasNotifications) {
     }
 
+    /** Sets whether the squishiness fraction should be updated on the media host. */
+    default void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {}
+
     /**
      * Should touches from the notification panel be disallowed?
      * The notification panel might grab any touches rom QS at any time to collapse the shade.
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
deleted file mode 100644
index 4a2a1cb..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2022, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
--->
-
-<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/footer_actions_height"
-    android:elevation="@dimen/qs_panel_elevation"
-    android:paddingTop="@dimen/qs_footer_actions_top_padding"
-    android:paddingBottom="@dimen/qs_footer_actions_bottom_padding"
-    android:background="@drawable/qs_footer_actions_background"
-    android:gravity="center_vertical|end"
-    android:layout_gravity="bottom"
-/>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml
deleted file mode 100644
index fad41c82..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/qs_footer_action_button_size"
-    android:layout_height="@dimen/qs_footer_action_button_size"
-    android:visibility="gone">
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="@dimen/qs_footer_icon_size"
-        android:layout_height="@dimen/qs_footer_icon_size"
-        android:layout_gravity="center"
-        android:scaleType="centerInside" />
-</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
deleted file mode 100644
index c09607d..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/qs_footer_action_button_size"
-    android:layout_height="@dimen/qs_footer_action_button_size"
-    android:background="@drawable/qs_footer_action_circle"
-    android:visibility="gone">
-    <TextView
-        android:id="@+id/number"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-        android:layout_gravity="center"
-        android:textColor="?attr/onShadeInactiveVariant"
-        android:textSize="18sp"/>
-    <ImageView
-        android:id="@+id/new_dot"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:scaleType="fitCenter"
-        android:layout_gravity="bottom|end"
-        android:src="@drawable/fgs_dot"
-        android:contentDescription="@string/fgs_dot_content_description" />
-</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
deleted file mode 100644
index 1c31f1d..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.systemui.animation.view.LaunchableLinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="@dimen/qs_security_footer_single_line_height"
-    android:layout_weight="1"
-    android:orientation="horizontal"
-    android:paddingHorizontal="@dimen/qs_footer_padding"
-    android:gravity="center_vertical"
-    android:layout_marginEnd="@dimen/qs_footer_action_inset"
-    android:background="@drawable/qs_security_footer_background"
-    android:visibility="gone">
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="@dimen/qs_footer_icon_size"
-        android:layout_height="@dimen/qs_footer_icon_size"
-        android:gravity="start"
-        android:layout_marginEnd="12dp"
-        android:contentDescription="@null"
-        android:src="@drawable/ic_info_outline"
-        android:tint="?attr/onSurfaceVariant" />
-
-    <TextView
-        android:id="@+id/text"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:maxLines="1"
-        android:ellipsize="end"
-        android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-        android:textColor="?attr/onSurfaceVariant"/>
-
-    <ImageView
-        android:id="@+id/new_dot"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:scaleType="fitCenter"
-        android:src="@drawable/fgs_dot"
-        android:contentDescription="@string/fgs_dot_content_description"
-        />
-
-    <ImageView
-        android:id="@+id/chevron_icon"
-        android:layout_width="@dimen/qs_footer_icon_size"
-        android:layout_height="@dimen/qs_footer_icon_size"
-        android:layout_marginStart="8dp"
-        android:contentDescription="@null"
-        android:src="@*android:drawable/ic_chevron_end"
-        android:autoMirrored="true"
-        android:tint="?attr/onSurfaceVariant" />
-</com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml
deleted file mode 100644
index a8abd79..0000000
--- a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" />
-    <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" />
-    <item android:color="@color/transparent" />
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
deleted file mode 100644
index 0881d7c..0000000
--- a/packages/SystemUI/res/drawable/fgs_dot.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2022, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:shape="oval"
-    android:width="12dp"
-    android:height="12dp">
-    <solid android:color="?attr/tertiary" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
deleted file mode 100644
index 4a5d4af..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:inset="@dimen/qs_footer_action_inset">
-    <ripple
-        android:color="?android:attr/colorControlHighlight">
-        <item android:id="@android:id/mask">
-            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
-            <!-- properly into an app/dialog. -->
-            <shape android:shape="rectangle">
-                <solid android:color="@android:color/white"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <solid android:color="?attr/shadeInactive"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-
-    </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
deleted file mode 100644
index 47a2965..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:inset="@dimen/qs_footer_action_inset">
-    <ripple
-        android:color="?android:attr/colorControlHighlight">
-        <item android:id="@android:id/mask">
-            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
-            <!-- properly into an app/dialog. -->
-            <shape android:shape="rectangle">
-                <solid android:color="@android:color/white"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <solid android:color="?attr/shadeActive"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <solid android:color="@color/qs_footer_power_button_overlay_color"/>
-                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
-            </shape>
-        </item>
-
-    </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
deleted file mode 100644
index 0b0055b..0000000
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset"
-    >
-    <ripple
-        android:color="?android:attr/colorControlHighlight">
-        <item android:id="@android:id/mask">
-            <shape android:shape="rectangle">
-                <solid android:color="@android:color/white"/>
-                <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
-            </shape>
-        </item>
-        <item>
-            <shape android:shape="rectangle">
-                <stroke android:width="1dp"
-                        android:color="?attr/shadeInactive"/>
-                <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
-            </shape>
-        </item>
-    </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml b/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml
deleted file mode 100644
index 29a014a..0000000
--- a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorAccentPrimary" />
-    <corners android:radius="40dp" />
-</shape>
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
deleted file mode 100644
index 8b9eabc..0000000
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ /dev/null
@@ -1,237 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/biometric_prompt_constraint_layout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ImageView
-        android:id="@+id/background"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:contentDescription="@string/biometric_dialog_empty_space_description"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <View
-        android:id="@+id/panel"
-        style="@style/AuthCredentialPanelStyle"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
-        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
-        app:layout_constraintTop_toTopOf="@+id/topBarrier"
-        app:layout_constraintWidth_max="640dp" />
-
-    <include
-        android:id="@+id/button_bar"
-        layout="@layout/biometric_prompt_button_bar"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintBottom_toBottomOf="@id/bottomGuideline"
-        app:layout_constraintEnd_toEndOf="@id/panel"
-        app:layout_constraintStart_toStartOf="@id/panel" />
-
-    <ScrollView
-        android:id="@+id/scrollView"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:fadeScrollbars="false"
-        android:fillViewport="true"
-        android:paddingBottom="32dp"
-        android:paddingHorizontal="32dp"
-        android:paddingTop="24dp"
-        app:layout_constrainedHeight="true"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
-        app:layout_constraintEnd_toEndOf="@id/panel"
-        app:layout_constraintStart_toStartOf="@id/panel"
-        app:layout_constraintTop_toTopOf="@+id/topGuideline"
-        app:layout_constraintVertical_bias="1.0">
-
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/innerConstraint"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <ImageView
-                android:id="@+id/logo"
-                android:layout_width="@dimen/biometric_prompt_logo_size"
-                android:layout_height="@dimen/biometric_prompt_logo_size"
-                android:layout_gravity="center"
-                android:contentDescription="@string/biometric_dialog_logo"
-                android:scaleType="fitXY"
-                android:visibility="visible"
-                app:layout_constraintBottom_toTopOf="@+id/logo_description"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <TextView
-                android:id="@+id/logo_description"
-                style="@style/TextAppearance.AuthCredential.LogoDescription"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="16dp"
-                app:layout_constraintBottom_toTopOf="@+id/title"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/logo" />
-
-            <TextView
-                android:id="@+id/title"
-                style="@style/TextAppearance.AuthCredential.Title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                android:paddingTop="16dp"
-                app:layout_constraintBottom_toTopOf="@+id/subtitle"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/logo_description" />
-
-            <TextView
-                android:id="@+id/subtitle"
-                style="@style/TextAppearance.AuthCredential.Subtitle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                android:paddingTop="16dp"
-                app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/title" />
-
-            <LinearLayout
-                android:id="@+id/customized_view_container"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="vertical"
-                android:paddingTop="24dp"
-                android:visibility="gone"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
-            <TextView
-                android:id="@+id/description"
-                style="@style/TextAppearance.AuthCredential.Description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="@integer/biometric_dialog_text_gravity"
-                android:paddingTop="16dp"
-                android:textAlignment="viewStart"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
-            <androidx.constraintlayout.widget.Barrier
-                android:id="@+id/contentBarrier"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:barrierAllowsGoneWidgets="false"
-                app:barrierDirection="top"
-                app:constraint_referenced_ids="description, customized_view_container" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-    </ScrollView>
-
-    <!-- Cancel Button, replaces negative button when biometric is accepted -->
-    <TextView
-        android:id="@+id/indicator"
-        style="@style/TextAppearance.AuthCredential.Indicator"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="24dp"
-        android:accessibilityLiveRegion="assertive"
-        android:fadingEdge="horizontal"
-        android:gravity="center_horizontal"
-        android:scrollHorizontally="true"
-        app:layout_constraintBottom_toTopOf="@+id/button_bar"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel"
-        app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
-        app:layout_constraintVertical_bias="0.0" />
-
-    <!-- "Use Credential" Button, replaces if device credential is allowed -->
-
-    <!-- Positive Button -->
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/topBarrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="top"
-        app:constraint_referenced_ids="scrollView" />
-
-    <!-- Try Again Button -->
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/scrollBarrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="top"
-        app:constraint_referenced_ids="biometric_icon, button_bar" />
-
-    <!-- Guidelines for setting panel border -->
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/leftGuideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/rightGuideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/bottomGuideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_end="40dp" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/topGuideline"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_begin="56dp" />
-
-    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-        android:id="@+id/biometric_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_bias="1.0"
-        tools:srcCompat="@tools:sample/avatars" />
-
-    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-        android:id="@+id/biometric_icon_overlay"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_gravity="center"
-        android:contentDescription="@null"
-        android:scaleType="fitXY"
-        android:importantForAccessibility="no"
-        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
-        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
-        app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
index 292e496..06d1bf4 100644
--- a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
+++ b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
@@ -5,9 +5,9 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <LinearLayout
+    <FrameLayout
         android:id="@+id/shortcut_helper_sheet"
-        style="@style/Widget.Material3.BottomSheet"
+        style="@style/ShortcutHelperBottomSheet"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
@@ -19,13 +19,9 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
 
-        <TextView
+        <androidx.compose.ui.platform.ComposeView
+            android:id="@+id/shortcut_helper_compose_container"
             android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:gravity="center"
-            android:textAppearance="?textAppearanceDisplayLarge"
-            android:background="?colorTertiaryContainer"
-            android:text="Shortcut Helper Content" />
-    </LinearLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+            android:layout_height="match_parent" />
+    </FrameLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
similarity index 92%
rename from packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
rename to packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
index 9b5b59f..8d50bfa 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
@@ -22,9 +22,11 @@
         android:layout_width="0dp"
         android:layout_height="0dp"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="@id/rightGuideline"
+        app:layout_constraintEnd_toStartOf="@id/rightGuideline"
         app:layout_constraintStart_toStartOf="@id/leftGuideline"
-        app:layout_constraintTop_toTopOf="@id/topBarrier" />
+        app:layout_constraintTop_toTopOf="@id/topBarrier"
+        app:layout_constraintWidth_max="@dimen/biometric_prompt_panel_max_width" />
+
 
     <include
         android:id="@+id/button_bar"
@@ -41,8 +43,8 @@
         android:layout_height="wrap_content"
         android:fillViewport="true"
         android:fadeScrollbars="false"
-        android:paddingBottom="24dp"
-        android:paddingHorizontal="24dp"
+        android:paddingBottom="@dimen/biometric_prompt_top_scroll_view_bottom_padding"
+        android:paddingHorizontal="@dimen/biometric_prompt_top_scroll_view_horizontal_padding"
         android:paddingTop="24dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
@@ -76,7 +78,7 @@
                 style="@style/TextAppearance.AuthCredential.LogoDescription"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:paddingTop="8dp"
+                android:paddingTop="@dimen/biometric_prompt_logo_description_top_padding"
                 app:layout_constraintBottom_toTopOf="@+id/title"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -180,14 +182,14 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        app:layout_constraintGuide_begin="0dp" />
+        app:layout_constraintGuide_begin="@dimen/biometric_prompt_one_pane_medium_horizontal_guideline_padding" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/rightGuideline"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        app:layout_constraintGuide_end="0dp" />
+        app:layout_constraintGuide_end="@dimen/biometric_prompt_one_pane_medium_horizontal_guideline_padding" />
 
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/bottomGuideline"
@@ -201,7 +203,7 @@
         android:layout_width="0dp"
         android:layout_height="0dp"
         android:orientation="horizontal"
-        app:layout_constraintGuide_begin="119dp" />
+        app:layout_constraintGuide_begin="@dimen/biometric_prompt_one_pane_medium_top_guideline_padding" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
         android:id="@+id/biometric_icon"
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
similarity index 100%
rename from packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
rename to packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml
deleted file mode 100644
index f45cc7c..0000000
--- a/packages/SystemUI/res/layout/people_space_activity.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at
-         runtime depending on the number of conversations to show. -->
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml b/packages/SystemUI/res/layout/people_space_activity_list_divider.xml
deleted file mode 100644
index 3b9fb3b..0000000
--- a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<View
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="2dp"
-    android:background="?android:attr/colorBackground" />
diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
deleted file mode 100644
index a97c90c..0000000
--- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/top_level_no_conversations"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="24dp"
-    android:clipToOutline="true">
-    <TextView
-        android:id="@+id/select_conversation_title"
-        android:gravity="center"
-        android:text="@string/select_conversation_title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_centerHorizontal="true"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="24sp"
-        android:layout_alignParentTop="true" />
-
-    <TextView
-        android:id="@+id/select_conversation"
-        android:gravity="center"
-        android:text="@string/no_conversations_text"
-        android:layout_width="match_parent"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="16sp"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:padding="24dp"
-        android:layout_marginTop="26dp"
-        android:layout_below="@id/select_conversation_title"/>
-
-    <Button
-        style="?android:attr/buttonBarButtonStyle"
-        android:id="@+id/got_it_button"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:background="@drawable/rounded_bg_full_large_radius"
-        android:text="@string/got_it"
-        android:textColor="?androidprv:attr/textColorOnAccent"
-        android:layout_marginBottom="60dp"
-        android:layout_alignParentBottom="true" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_above="@id/got_it_button"
-        android:layout_below="@id/select_conversation"
-        android:layout_centerInParent="true"
-        android:clipToOutline="true">
-        <LinearLayout
-            android:id="@+id/widget_initial_layout"
-            android:layout_width="200dp"
-            android:layout_height="100dp"
-            android:layout_gravity="center"
-            android:background="@drawable/rounded_bg_full_large_radius"
-            android:layout_above="@id/got_it_button">
-            <include layout="@layout/people_space_placeholder_layout" />
-        </LinearLayout>
-    </LinearLayout>
-</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
deleted file mode 100644
index 2384963..0000000
--- a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
+++ /dev/null
@@ -1,115 +0,0 @@
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/top_level_with_conversations"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="8dp">
-    <TextView
-        android:id="@+id/select_conversation_title"
-        android:text="@string/select_conversation_title"
-        android:gravity="center"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="24sp"/>
-
-    <TextView
-        android:id="@+id/select_conversation"
-        android:text="@string/select_conversation_text"
-        android:gravity="center"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="16sp"
-        android:paddingVertical="24dp"
-        android:paddingHorizontal="48dp"/>
-
-    <androidx.core.widget.NestedScrollView
-        android:id="@+id/scroll_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:id="@+id/scroll_layout"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="16dp"
-            android:orientation="vertical">
-
-            <LinearLayout
-                android:id="@+id/priority"
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="35dp">
-                <TextView
-                    android:id="@+id/priority_header"
-                    android:text="@string/priority_conversations"
-                    android:layout_width="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
-                    android:textSize="14sp"
-                    android:paddingStart="16dp"
-                    android:layout_height="wrap_content"/>
-
-                <LinearLayout
-                    android:id="@+id/priority_tiles"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="10dp"
-                    android:orientation="vertical"
-                    android:background="@drawable/rounded_bg_full_large_radius"
-                    android:clipToOutline="true">
-                </LinearLayout>
-            </LinearLayout>
-
-            <LinearLayout
-                android:id="@+id/recent"
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TextView
-                    android:id="@+id/recent_header"
-                    android:gravity="start"
-                    android:text="@string/recent_conversations"
-                    android:layout_width="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
-                    android:textSize="14sp"
-                    android:paddingStart="16dp"
-                    android:layout_height="wrap_content"/>
-
-                <LinearLayout
-                    android:id="@+id/recent_tiles"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="10dp"
-                    android:orientation="vertical"
-                    android:background="@drawable/rounded_bg_full_large_radius"
-                    android:clipToOutline="true">
-                </LinearLayout>
-            </LinearLayout>
-        </LinearLayout>
-    </androidx.core.widget.NestedScrollView>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml
deleted file mode 100644
index b0599ca..0000000
--- a/packages/SystemUI/res/layout/people_space_tile_view.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/tile_view"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:orientation="vertical"
-        android:background="?androidprv:attr/colorSurface"
-        android:padding="12dp"
-        android:elevation="4dp"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <LinearLayout
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="start">
-
-            <ImageView
-                android:id="@+id/tile_view_person_icon"
-                android:layout_width="@dimen/avatar_size_for_medium"
-                android:layout_height="@dimen/avatar_size_for_medium" />
-
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical">
-
-                <TextView
-                    android:id="@+id/tile_view_name"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:paddingHorizontal="16dp"
-                    android:textSize="22sp"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_vertical"/>
-            </LinearLayout>
-        </LinearLayout>
-    </LinearLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index e3c5a7d..5f77f61 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,13 +47,12 @@
 
     <include layout="@layout/quick_status_bar_expanded_header" />
 
-    <include
-        layout="@layout/footer_actions"
+    <androidx.compose.ui.platform.ComposeView
         android:id="@+id/qs_footer_actions"
         android:layout_height="@dimen/footer_actions_height"
         android:layout_width="match_parent"
         android:layout_gravity="bottom"
-        />
+        android:elevation="@dimen/qs_panel_elevation" />
 
     <include
         android:id="@+id/qs_customize"
diff --git a/packages/SystemUI/res/raw/face_dialog_authenticating.json b/packages/SystemUI/res/raw/face_dialog_authenticating.json
deleted file mode 100644
index 4e25e6d..0000000
--- a/packages/SystemUI/res/raw/face_dialog_authenticating.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.7.13","fr":60,"ip":0,"op":61,"w":64,"h":64,"nm":"face_scanning 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,32,0],"ix":2,"l":2},"a":{"a":0,"k":[27.25,27.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[95,95,100]},{"t":60,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.244,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.244,0]],"v":[[-2.249,0.001],[0.001,2.251],[2.249,0.001],[0.001,-2.251]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.1,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.242,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.242,0]],"v":[[-2.249,0],[0.001,2.25],[2.249,0],[0.001,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[39.4,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[2.814,3.523],[-2.814,3.523],[-2.814,1.363],[0.652,1.363],[0.652,-3.523],[2.814,-3.523]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.791,28.479],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.154,0.15],[0,0],[0.117,-0.095],[0,0],[0.228,-0.121],[0.358,-0.103],[0.922,0.261],[0.3,0.16],[0.24,0.185],[0.14,0.139],[0.178,0.261],[0.143,0.451],[0,0],[0,0.494],[0,0],[-0.214,-0.676],[-0.392,-0.572],[-0.323,-0.317],[-0.228,-0.177],[-0.333,-0.179],[-0.503,-0.145],[-0.662,0],[-0.653,0.184],[-0.437,0.233],[-0.336,0.258],[0,0],[0,0]],"o":[[0,0],[-0.107,0.106],[0,0],[-0.24,0.185],[-0.301,0.16],[-0.92,0.261],[-0.357,-0.103],[-0.228,-0.121],[-0.158,-0.122],[-0.225,-0.221],[-0.272,-0.393],[0,0],[-0.147,-0.466],[0,0],[0,0.716],[0.206,0.656],[0.256,0.372],[0.204,0.201],[0.336,0.258],[0.436,0.233],[0.655,0.184],[0.662,0],[0.503,-0.145],[0.332,-0.179],[0,0],[0,0],[0.165,-0.136]],"v":[[6.094,1.465],[4.579,-0.076],[4.242,0.225],[4.124,0.315],[3.43,0.771],[2.439,1.165],[-0.342,1.165],[-1.331,0.771],[-2.027,0.315],[-2.48,-0.075],[-3.087,-0.801],[-3.712,-2.075],[-3.712,-2.075],[-3.934,-3.523],[-6.094,-3.523],[-5.771,-1.424],[-4.868,0.424],[-3.995,1.465],[-3.344,2.027],[-2.35,2.676],[-0.934,3.243],[1.049,3.523],[3.031,3.243],[4.449,2.676],[5.441,2.027],[5.482,1.997],[5.615,1.895]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[26.201,40.411],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.398,0],[0,-13.4],[13.398,0],[0,13.4]],"o":[[13.398,0],[0,13.4],[-13.398,0],[0,-13.4]],"v":[[0,-24.3],[24.3,0],[0,24.3],[-24.3,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[14.904,0],[0,-14.904],[-14.904,0],[0,14.904]],"o":[[-14.904,0],[0,14.904],[14.904,0],[0,-14.904]],"v":[[0,-27],[-27,0],[0,27],[27,0]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.25,27.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 56ebc06..aea79e8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -28,9 +28,7 @@
 
     <!--  In landscape the security footer is actually part of the header,
     and needs to be as short as the header  -->
-    <dimen name="qs_security_footer_single_line_height">@*android:dimen/quick_qs_offset_height</dimen>
     <dimen name="qs_footer_padding">14dp</dimen>
-    <dimen name="qs_security_footer_background_inset">12dp</dimen>
 
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
     <dimen name="volume_row_slider_height">128dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 2cfba01..29e0dbe 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,11 +68,6 @@
 
     <dimen name="qs_brightness_margin_bottom">16dp</dimen>
 
-    <!--  For large screens the security footer appears below the footer,
-    same as phones in portrait  -->
-    <dimen name="qs_security_footer_single_line_height">48dp</dimen>
-    <dimen name="qs_security_footer_background_inset">0dp</dimen>
-
     <dimen name="qs_panel_padding_top">8dp</dimen>
 
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
@@ -102,6 +97,17 @@
     <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
     <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
 
+    <!-- Dimensions for biometric prompt panel padding -->
+    <dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">56dp</dimen>
+    <dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">@dimen/biometric_dialog_border_padding</dimen>
+
+    <!-- Dimensions for biometric prompt scroll view padding -->
+    <dimen name="biometric_prompt_top_scroll_view_bottom_padding">32dp</dimen>
+    <dimen name="biometric_prompt_top_scroll_view_horizontal_padding">32dp</dimen>
+
+    <!-- Dimensions for biometric prompt custom content view. -->
+    <dimen name="biometric_prompt_logo_description_top_padding">16dp</dimen>
+
     <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
     <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index edd3d77..8ce2068 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -713,7 +713,6 @@
     <dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
     <dimen name="qs_header_carrier_separator_width">6dp</dimen>
     <dimen name="qs_carrier_margin_width">4dp</dimen>
-    <dimen name="qs_footer_icon_size">20dp</dimen>
     <dimen name="qs_header_height">120dp</dimen>
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
@@ -721,11 +720,7 @@
     <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen>
 
     <dimen name="qs_footer_padding">20dp</dimen>
-    <dimen name="qs_security_footer_height">88dp</dimen>
-    <dimen name="qs_security_footer_single_line_height">48dp</dimen>
     <dimen name="qs_footers_margin_bottom">8dp</dimen>
-    <dimen name="qs_security_footer_background_inset">0dp</dimen>
-    <dimen name="qs_security_footer_corner_radius">28dp</dimen>
 
     <dimen name="segmented_button_spacing">0dp</dimen>
     <dimen name="borderless_button_radius">2dp</dimen>
@@ -1119,11 +1114,18 @@
     <dimen name="biometric_dialog_height">240dp</dimen>
 
     <!-- Dimensions for biometric prompt panel padding -->
-    <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen>
-    <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen>
-    <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen>
-    <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen>
-    <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen>
+    <dimen name="biometric_prompt_panel_max_width">640dp</dimen>
+    <dimen name="biometric_prompt_land_small_horizontal_guideline_padding">344dp</dimen>
+    <dimen name="biometric_prompt_two_pane_udfps_horizontal_guideline_padding">114dp</dimen>
+    <dimen name="biometric_prompt_two_pane_udfps_mid_guideline_padding">409dp</dimen>
+    <dimen name="biometric_prompt_two_pane_medium_horizontal_guideline_padding">640dp</dimen>
+    <dimen name="biometric_prompt_two_pane_medium_mid_guideline_padding">330dp</dimen>
+    <dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">119dp</dimen>
+    <dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">0dp</dimen>
+
+    <!-- Dimensions for biometric prompt scroll view padding -->
+    <dimen name="biometric_prompt_top_scroll_view_bottom_padding">24dp</dimen>
+    <dimen name="biometric_prompt_top_scroll_view_horizontal_padding">24dp</dimen>
 
     <!-- Dimensions for biometric prompt icon padding -->
     <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen>
@@ -1136,6 +1138,7 @@
 
     <!-- Dimensions for biometric prompt custom content view. -->
     <dimen name="biometric_prompt_logo_size">32dp</dimen>
+    <dimen name="biometric_prompt_logo_description_top_padding">8dp</dimen>
     <dimen name="biometric_prompt_content_corner_radius">28dp</dimen>
     <dimen name="biometric_prompt_content_padding_horizontal">24dp</dimen>
     <dimen name="biometric_prompt_content_padding_vertical">16dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index b993a5a..177ba598 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -259,9 +259,6 @@
     <!-- ID of the Scene Container root Composable view -->
     <item type='id' name="scene_container_root_composable" />
 
-    <!-- Tag set on the Compose implementation of the QS footer actions. -->
-    <item type="id" name="tag_compose_qs_footer_actions" />
-
     <!--
     Ids for the device entry icon.
         device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c038a82..1226bbf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3489,6 +3489,45 @@
     <!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
     <string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
 
+    <!-- Title of the keyboard shortcut helper category "System". The helper is a component that
+         shows the user which keyboard shortcuts they can use. The "System" shortcuts are for
+         example "Take a screenshot" or "Go back". [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_category_system">System</string>
+    <!-- Title of the keyboard shortcut helper category "Multitasking". The helper is a component
+         that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are
+         for example "Enter split screen". [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_category_multitasking">Multitasking</string>
+    <!-- Title of the keyboard shortcut helper category "Input". The helper is a component
+         that shows the user which keyboard shortcuts they can use. The "Input" shortcuts are
+         the ones provided by the keyboard. Examples are "Access emoji" or "Switch to next language"
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_category_input">Input</string>
+    <!-- Title of the keyboard shortcut helper category "App shortcuts". The helper is a component
+         that shows the user which keyboard shortcuts they can use. The "App shortcuts" are
+         for example "Open browser" or "Open calculator". [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_category_app_shortcuts">App shortcuts</string>
+    <!-- Title of the keyboard shortcut helper category "Accessibility". The helper is a component
+         that shows the user which keyboard shortcuts they can use. The "Accessibility" shortcuts
+         are for example "Turn on talkback". [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_category_a11y">Accessibility</string>
+    <!-- Title at the top of the keyboard shortcut helper UI. The helper is a component
+         that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_title">Keyboard shortcuts</string>
+    <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
+         hasn't typed in anything in the search box yet. The helper is a  component that shows the
+         user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_search_placeholder">Search shortcuts</string>
+    <!-- Content description of the icon that allows to collapse a keyboard shortcut helper category
+         panel. The helper is a  component that shows the  user which keyboard shortcuts they can
+         use. The helper shows shortcuts in categories, which can be collapsed or expanded.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string>
+    <!-- Content description of the icon that allows to expand a keyboard shortcut helper category
+         panel. The helper is a  component that shows the  user which keyboard shortcuts they can
+         use. The helper shows shortcuts in categories, which can be collapsed or expanded.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_content_description_expand_icon">Expand icon</string>
+
     <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
     <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
     <!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b8f71c1..1e0adec 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -149,11 +149,6 @@
         <item name="android:letterSpacing">0.01</item>
     </style>
 
-    <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
-        <item name="android:textColor">?attr/onSurface</item>
-    </style>
-
     <style name="TextAppearance.QS.Status.Carriers" />
 
     <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
@@ -1670,6 +1665,10 @@
         <item name="android:colorBackground">@color/transparent</item>
     </style>
 
+    <style name="ShortcutHelperBottomSheet" parent="@style/Widget.Material3.BottomSheet">
+        <item name="backgroundTint">?colorSurfaceContainer</item>
+    </style>
+
     <style name="ShortcutHelperAnimation" parent="@android:style/Animation.Activity">
         <item name="android:activityOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item>
         <item name="android:taskOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index b99c514..44f2059 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -22,15 +22,28 @@
     data class Biometric(
         /** The available modalities for the authentication on the prompt. */
         val activeModalities: BiometricModalities = BiometricModalities(),
-        // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of
-        // simply depending on rotations.
-        val showTwoPane: Boolean = false,
-    ) : PromptKind
+        val paneType: PaneType = PaneType.ONE_PANE_PORTRAIT,
+    ) : PromptKind {
+        enum class PaneType {
+            TWO_PANE_LANDSCAPE,
+            ONE_PANE_PORTRAIT,
+            ONE_PANE_NO_SENSOR_LANDSCAPE,
+            ONE_PANE_LARGE_SCREEN_LANDSCAPE
+        }
+    }
 
-    object Pin : PromptKind
-    object Pattern : PromptKind
-    object Password : PromptKind
+    data object Pin : PromptKind
+    data object Pattern : PromptKind
+    data object Password : PromptKind
 
     fun isBiometric() = this is Biometric
-    fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password)
+    fun isTwoPaneLandscapeBiometric(): Boolean =
+        (this as? Biometric)?.paneType == Biometric.PaneType.TWO_PANE_LANDSCAPE
+    fun isOnePanePortraitBiometric() =
+        (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_PORTRAIT
+    fun isOnePaneNoSensorLandscapeBiometric() =
+        (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE
+    fun isOnePaneLargeScreenLandscapeBiometric() =
+        (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE
+    fun isCredential() = (this is Pin) || (this is Pattern) || (this is Password)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3d7accb..1ee4908 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -29,6 +29,7 @@
 import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
@@ -360,15 +361,23 @@
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
                 Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
 
+        final boolean isLandscape = mContext.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
         mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
         mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId,
                 getRequestId(), biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName,
-                false /*onSwitchToCredential*/);
+                false /*onSwitchToCredential*/, isLandscape);
 
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) {
-            mLayout = (ConstraintLayout) layoutInflater.inflate(
-                    R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
+        final PromptKind kind = mPromptViewModel.getPromptKind().getValue();
+        if (constraintBp() && kind.isBiometric()) {
+            if (kind.isTwoPaneLandscapeBiometric()) {
+                mLayout = (ConstraintLayout) layoutInflater.inflate(
+                        R.layout.biometric_prompt_two_pane_layout, this, false /* attachToRoot */);
+            } else {
+                mLayout = (ConstraintLayout) layoutInflater.inflate(
+                        R.layout.biometric_prompt_one_pane_layout, this, false /* attachToRoot */);
+            }
         } else {
             mLayout = (FrameLayout) layoutInflater.inflate(
                     R.layout.auth_container_view, this, false /* attachToRoot */);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 8e5a97b..9b14d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -29,11 +29,10 @@
 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY
 import com.android.systemui.display.data.repository.DisplayRepository
 import javax.inject.Inject
+import kotlin.math.min
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -58,7 +57,7 @@
     val currentDisplaySize: StateFlow<Size>
 
     /** Provides whether the current display is large screen */
-    val isLargeScreen: Flow<Boolean>
+    val isLargeScreen: StateFlow<Boolean>
 }
 
 @SysUISingleton
@@ -127,16 +126,29 @@
                     ),
             )
 
-    override val isLargeScreen: Flow<Boolean> =
+    override val isLargeScreen: StateFlow<Boolean> =
         currentDisplayInfo
             .map {
-                // TODO: This works, but investigate better way to handle this
-                it.logicalWidth * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXXHIGH &&
-                    it.logicalHeight * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXHIGH
+                // copied from systemui/shared/...Utilities.java
+                val smallestWidth =
+                    dpiFromPx(
+                        min(it.logicalWidth, it.logicalHeight).toFloat(),
+                        context.resources.configuration.densityDpi
+                    )
+                smallestWidth >= LARGE_SCREEN_MIN_DPS
             }
-            .distinctUntilChanged()
+            .stateIn(
+                backgroundScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+    private fun dpiFromPx(size: Float, densityDpi: Int): Float {
+        val densityRatio = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT
+        return size / densityRatio
+    }
 
     companion object {
         const val TAG = "DisplayStateRepositoryImpl"
+        const val LARGE_SCREEN_MIN_DPS = 600f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 591da40..40313e3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -65,7 +65,8 @@
     /** Called on configuration changes, used to keep the display state in sync */
     fun onConfigurationChanged(newConfig: Configuration)
 
-    val isLargeScreen: Flow<Boolean>
+    /** Provides whether the current display is large screen */
+    val isLargeScreen: StateFlow<Boolean>
 }
 
 /** Encapsulates logic for interacting with the display state. */
@@ -127,7 +128,7 @@
 
     override val isDefaultDisplayOff = displayRepository.defaultDisplayOff
 
-    override val isLargeScreen: Flow<Boolean> = displayStateRepository.isLargeScreen
+    override val isLargeScreen: StateFlow<Boolean> = displayStateRepository.isLargeScreen
 
     companion object {
         private const val TAG = "DisplayStateInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index dc338d0..c08756f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -91,6 +91,7 @@
         challenge: Long,
         opPackageName: String,
         onSwitchToCredential: Boolean,
+        isLandscape: Boolean,
     )
 
     /** Unset the current authentication request. */
@@ -102,6 +103,7 @@
 @Inject
 constructor(
     fingerprintPropertyRepository: FingerprintPropertyRepository,
+    private val displayStateInteractor: DisplayStateInteractor,
     private val promptRepository: PromptRepository,
     private val lockPatternUtils: LockPatternUtils,
 ) : PromptSelectorInteractor {
@@ -166,7 +168,9 @@
             modalities,
             promptRepository.challenge.value!!,
             promptRepository.opPackageName.value!!,
-            true /*onSwitchToCredential*/
+            onSwitchToCredential = true,
+            // isLandscape value is not important when onSwitchToCredential is true
+            isLandscape = false,
         )
     }
 
@@ -178,6 +182,7 @@
         challenge: Long,
         opPackageName: String,
         onSwitchToCredential: Boolean,
+        isLandscape: Boolean,
     ) {
         val hasCredentialViewShown = promptKind.value.isCredential()
         val showBpForCredential =
@@ -189,11 +194,30 @@
                 !promptInfo.isContentViewMoreOptionsButtonUsed
         val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown
         var kind: PromptKind = PromptKind.None
+
         if (onSwitchToCredential) {
             kind = getCredentialType(lockPatternUtils, effectiveUserId)
         } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) {
-            // TODO(b/330908557): check to show one pane or two pane
-            kind = PromptKind.Biometric(modalities)
+            // TODO(b/330908557): Subscribe to
+            // displayStateInteractor.currentRotation.value.isDefaultOrientation() for checking
+            // `isLandscape` after removing AuthContinerView.
+            kind =
+                if (isLandscape) {
+                    val paneType =
+                        when {
+                            displayStateInteractor.isLargeScreen.value ->
+                                PromptKind.Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE
+                            showBpWithoutIconForCredential ->
+                                PromptKind.Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE
+                            else -> PromptKind.Biometric.PaneType.TWO_PANE_LANDSCAPE
+                        }
+                    PromptKind.Biometric(
+                        modalities,
+                        paneType = paneType,
+                    )
+                } else {
+                    PromptKind.Biometric(modalities)
+                }
         } else if (isDeviceCredentialAllowed(promptInfo)) {
             kind = getCredentialType(lockPatternUtils, effectiveUserId)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 47174c0..c836f89 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -93,6 +93,7 @@
 
         if (constraintBp()) {
             val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
+            val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
             val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
             val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
 
@@ -355,6 +356,18 @@
                                 )
                             }
 
+                            if (bounds.top >= 0) {
+                                mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top)
+                                smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top)
+                            } else if (bounds.top < 0) {
+                                mediumConstraintSet.setGuidelineEnd(
+                                    topGuideline.id,
+                                    abs(bounds.top)
+                                )
+                                smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top))
+                            }
+
+                            // Use rect bottom to set mid guideline of two-pane.
                             if (midGuideline != null) {
                                 if (bounds.bottom >= 0) {
                                     midGuideline.setGuidelineEnd(bounds.bottom)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index fcc6992..9e836c3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -17,7 +17,9 @@
 
 package com.android.systemui.biometrics.ui.binder
 
+import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
@@ -28,8 +30,8 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import kotlinx.coroutines.flow.combine
@@ -61,6 +63,16 @@
                 }
 
                 var faceIcon: AnimatedVectorDrawable? = null
+                val faceIconCallback =
+                    object : Animatable2.AnimationCallback() {
+                        override fun onAnimationStart(drawable: Drawable) {
+                            viewModel.onAnimationStart()
+                        }
+
+                        override fun onAnimationEnd(drawable: Drawable) {
+                            viewModel.onAnimationEnd()
+                        }
+                    }
 
                 if (!constraintBp()) {
                     launch {
@@ -126,13 +138,19 @@
                             combine(
                                 viewModel.activeAuthType,
                                 viewModel.shouldAnimateIconView,
+                                viewModel.shouldRepeatAnimation,
                                 viewModel.showingError,
-                                ::Triple
+                                ::toQuad
                             ),
-                            ::toQuad
+                            ::toQuint
                         )
-                        .collect { (iconAsset, activeAuthType, shouldAnimateIconView, showingError)
-                            ->
+                        .collect {
+                            (
+                                iconAsset,
+                                activeAuthType,
+                                shouldAnimateIconView,
+                                shouldRepeatAnimation,
+                                showingError) ->
                             if (iconAsset != -1) {
                                 when (activeAuthType) {
                                     AuthType.Fingerprint,
@@ -145,27 +163,21 @@
                                         }
                                     }
                                     AuthType.Face -> {
-                                        // TODO(b/318569643): Consolidate logic once all face auth
-                                        // assets are migrated from drawable to json
-                                        if (iconAsset == R.raw.face_dialog_authenticating) {
-                                            iconView.setAnimation(iconAsset)
-                                            iconView.frame = 0
-
+                                        faceIcon?.apply {
+                                            unregisterAnimationCallback(faceIconCallback)
+                                            stop()
+                                        }
+                                        faceIcon =
+                                            iconView.context.getDrawable(iconAsset)
+                                                as AnimatedVectorDrawable
+                                        faceIcon?.apply {
+                                            iconView.setImageDrawable(this)
                                             if (shouldAnimateIconView) {
-                                                iconView.playAnimation()
-                                                iconView.loop(true)
-                                            }
-                                        } else {
-                                            faceIcon?.apply { stop() }
-                                            faceIcon =
-                                                iconView.context.getDrawable(iconAsset)
-                                                    as AnimatedVectorDrawable
-                                            faceIcon?.apply {
-                                                iconView.setImageDrawable(this)
-                                                if (shouldAnimateIconView) {
-                                                    forceAnimationOnUI()
-                                                    start()
+                                                forceAnimationOnUI()
+                                                if (shouldRepeatAnimation) {
+                                                    registerAnimationCallback(faceIconCallback)
                                                 }
+                                                start()
                                             }
                                         }
                                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 901d751..bde3e99 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -21,6 +21,7 @@
 import android.annotation.RawRes
 import android.content.res.Configuration
 import android.graphics.Rect
+import android.hardware.face.Face
 import android.util.RotationUtils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -31,10 +32,12 @@
 import com.android.systemui.util.kotlin.combine
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 /**
  * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
@@ -55,8 +58,11 @@
     }
 
     /**
-     * Indicates what auth type the UI currently displays. Fingerprint-only auth -> Fingerprint
-     * Face-only auth -> Face Co-ex auth, implicit flow -> Face Co-ex auth, explicit flow -> Coex
+     * Indicates what auth type the UI currently displays.
+     * Fingerprint-only auth -> Fingerprint
+     * Face-only auth -> Face
+     * Co-ex auth, implicit flow -> Face
+     * Co-ex auth, explicit flow -> Coex
      */
     val activeAuthType: Flow<AuthType> =
         combine(
@@ -113,6 +119,35 @@
         _previousIconOverlayWasError.value = previousIconOverlayWasError
     }
 
+    /** Called when iconView begins animating. */
+    fun onAnimationStart() {
+        _animationEnded.value = false
+    }
+
+    /** Called when iconView ends animating. */
+    fun onAnimationEnd() {
+        _animationEnded.value = true
+    }
+
+    private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /**
+     * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+     * ended).
+     */
+    val shouldPulseAnimation: Flow<Boolean> =
+        combine(_animationEnded, promptViewModel.isAuthenticating) {
+                animationEnded,
+                isAuthenticating ->
+                animationEnded && isAuthenticating
+            }
+            .distinctUntilChanged()
+
+    private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+    val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
     val iconSize: Flow<Pair<Int, Int>> =
         combine(
             promptViewModel.position,
@@ -160,22 +195,35 @@
                         }
                     }
                 AuthType.Face ->
-                    combine(
-                        promptViewModel.isAuthenticated.distinctUntilChanged(),
-                        promptViewModel.isAuthenticating.distinctUntilChanged(),
-                        promptViewModel.isPendingConfirmation.distinctUntilChanged(),
-                        promptViewModel.showingError.distinctUntilChanged()
-                    ) {
-                        authState: PromptAuthState,
-                        isAuthenticating: Boolean,
-                        isPendingConfirmation: Boolean,
-                        showingError: Boolean ->
-                        getFaceIconViewAsset(
-                            authState,
-                            isAuthenticating,
-                            isPendingConfirmation,
-                            showingError
-                        )
+                    shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+                        if (shouldPulseAnimation) {
+                            val iconAsset =
+                                if (_lastPulseLightToDark.value) {
+                                    R.drawable.face_dialog_pulse_dark_to_light
+                                } else {
+                                    R.drawable.face_dialog_pulse_light_to_dark
+                                }
+                            _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+                            flowOf(iconAsset)
+                        } else {
+                            combine(
+                                promptViewModel.isAuthenticated.distinctUntilChanged(),
+                                promptViewModel.isAuthenticating.distinctUntilChanged(),
+                                promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+                                promptViewModel.showingError.distinctUntilChanged()
+                            ) {
+                                authState: PromptAuthState,
+                                isAuthenticating: Boolean,
+                                isPendingConfirmation: Boolean,
+                                showingError: Boolean ->
+                                getFaceIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                            }
+                        }
                     }
                 AuthType.Coex ->
                     combine(
@@ -279,7 +327,8 @@
         } else if (authState.isAuthenticated) {
             R.drawable.face_dialog_dark_to_checkmark
         } else if (isAuthenticating) {
-            R.raw.face_dialog_authenticating
+            _lastPulseLightToDark.value = false
+            R.drawable.face_dialog_pulse_dark_to_light
         } else if (showingError) {
             R.drawable.face_dialog_dark_to_error
         } else if (_previousIconWasError.value) {
@@ -654,6 +703,16 @@
             }
         }
 
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+    val shouldRepeatAnimation: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex -> flowOf(false)
+                AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+            }
+        }
+
     /** Called on configuration changes */
     fun onConfigurationChanged(newConfig: Configuration) {
         displayStateInteractor.onConfigurationChanged(newConfig)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 156ec6b..c17b83d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -261,10 +261,13 @@
         combine(
                 _forceLargeSize,
                 displayStateInteractor.isLargeScreen,
-                displayStateInteractor.currentRotation
+                displayStateInteractor.currentRotation,
             ) { forceLarge, isLargeScreen, rotation ->
                 when {
-                    forceLarge || isLargeScreen -> PromptPosition.Bottom
+                    forceLarge ||
+                        isLargeScreen ||
+                        promptKind.value.isOnePaneNoSensorLandscapeBiometric() ->
+                        PromptPosition.Bottom
                     rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
                     rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
                     rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top
@@ -297,23 +300,27 @@
     /** Prompt panel size padding */
     private val smallHorizontalGuidelinePadding =
         context.resources.getDimensionPixelSize(
-            R.dimen.biometric_prompt_small_horizontal_guideline_padding
+            R.dimen.biometric_prompt_land_small_horizontal_guideline_padding
         )
     private val udfpsHorizontalGuidelinePadding =
         context.resources.getDimensionPixelSize(
-            R.dimen.biometric_prompt_udfps_horizontal_guideline_padding
+            R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding
         )
     private val udfpsMidGuidelinePadding =
         context.resources.getDimensionPixelSize(
-            R.dimen.biometric_prompt_udfps_mid_guideline_padding
+            R.dimen.biometric_prompt_two_pane_udfps_mid_guideline_padding
+        )
+    private val mediumTopGuidelinePadding =
+        context.resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding
         )
     private val mediumHorizontalGuidelinePadding =
         context.resources.getDimensionPixelSize(
-            R.dimen.biometric_prompt_medium_horizontal_guideline_padding
+            R.dimen.biometric_prompt_two_pane_medium_horizontal_guideline_padding
         )
     private val mediumMidGuidelinePadding =
         context.resources.getDimensionPixelSize(
-            R.dimen.biometric_prompt_medium_mid_guideline_padding
+            R.dimen.biometric_prompt_two_pane_medium_mid_guideline_padding
         )
 
     /** Rect for positioning biometric icon */
@@ -416,9 +423,9 @@
      * asset to be loaded before determining the prompt size.
      */
     val isIconViewLoaded: Flow<Boolean> =
-        combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded ->
-                val noIcon = modalities.isEmpty
-                noIcon || isIconViewLoaded
+        combine(hideSensorIcon, _isIconViewLoaded.asStateFlow()) { hideSensorIcon, isIconViewLoaded
+                ->
+                hideSensorIcon || isIconViewLoaded
             }
             .distinctUntilChanged()
 
@@ -448,17 +455,24 @@
      * from opposite side of the screen
      */
     val guidelineBounds: Flow<Rect> =
-        combine(iconPosition, size, position, modalities) { _, size, position, modalities ->
+        combine(iconPosition, promptKind, size, position, modalities) {
+                _,
+                promptKind,
+                size,
+                position,
+                modalities ->
                 when (position) {
-                    PromptPosition.Bottom -> Rect(0, 0, 0, 0)
+                    PromptPosition.Bottom ->
+                        if (promptKind.isOnePaneNoSensorLandscapeBiometric()) {
+                            Rect(0, 0, 0, 0)
+                        } else {
+                            Rect(0, mediumTopGuidelinePadding, 0, 0)
+                        }
                     PromptPosition.Right ->
                         if (size.isSmall) {
                             Rect(-smallHorizontalGuidelinePadding, 0, 0, 0)
                         } else if (modalities.hasUdfps) {
                             Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding)
-                        } else if (modalities.isEmpty) {
-                            // TODO: Temporary fix until no biometric landscape layout is added
-                            Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6)
                         } else {
                             Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding)
                         }
@@ -467,9 +481,6 @@
                             Rect(0, 0, -smallHorizontalGuidelinePadding, 0)
                         } else if (modalities.hasUdfps) {
                             Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding)
-                        } else if (modalities.isEmpty) {
-                            // TODO: Temporary fix until no biometric landscape layout is added
-                            Rect(0, 0, -mediumHorizontalGuidelinePadding, -6)
                         } else {
                             Rect(
                                 0,
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
index 2b9fc73..7a9429e 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
@@ -20,8 +20,15 @@
 import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositoryImpl
 import com.android.systemui.brightness.data.repository.ScreenBrightnessDisplayManagerRepository
 import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
+import com.android.systemui.brightness.shared.model.BrightnessLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
 @Module
 interface ScreenBrightnessModule {
@@ -33,4 +40,20 @@
 
     @Binds
     fun bindPolicyRepository(impl: BrightnessPolicyRepositoryImpl): BrightnessPolicyRepository
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @BrightnessLog
+        fun providesBrightnessTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("BrightnessTableLog", 50)
+        }
+
+        @Provides
+        @SysUISingleton
+        @BrightnessLog
+        fun providesBrightnessLog(factory: LogBufferFactory): LogBuffer {
+            return factory.create("BrightnessLog", 50)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
deleted file mode 100644
index 608f301..0000000
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.brightness.data.model
-
-@JvmInline
-value class LinearBrightness(val floatValue: Float) {
-    fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness {
-        return if (floatValue < min.floatValue) {
-            min
-        } else if (floatValue > max.floatValue) {
-            max
-        } else {
-            this
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
index 9ed11d1..37d1887 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -19,12 +19,18 @@
 import android.annotation.SuppressLint
 import android.hardware.display.BrightnessInfo
 import android.hardware.display.DisplayManager
-import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.BrightnessLog
+import com.android.systemui.brightness.shared.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.formatBrightness
+import com.android.systemui.brightness.shared.model.logDiffForTable
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.table.TableLogBuffer
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
@@ -32,13 +38,13 @@
 import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -78,6 +84,8 @@
 constructor(
     @DisplayId private val displayId: Int,
     private val displayManager: DisplayManager,
+    @BrightnessLog private val logBuffer: LogBuffer,
+    @BrightnessLog private val tableBuffer: TableLogBuffer,
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundContext: CoroutineContext,
 ) : ScreenBrightnessRepository {
@@ -100,6 +108,7 @@
                         displayManager.setBrightness(displayId, value)
                     }
                 }
+                logBrightnessChange(call is SetBrightnessMethod.Permanent, value)
             }
         }
     }
@@ -147,13 +156,15 @@
         brightnessInfo
             .filterNotNull()
             .map { LinearBrightness(it.brightnessMinimum) }
-            .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+            .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MIN, null)
+            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
 
-    override val maxLinearBrightness =
+    override val maxLinearBrightness: SharedFlow<LinearBrightness> =
         brightnessInfo
             .filterNotNull()
             .map { LinearBrightness(it.brightnessMaximum) }
-            .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+            .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MAX, null)
+            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(1f))
 
     override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
         val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue()
@@ -166,7 +177,8 @@
         brightnessInfo
             .filterNotNull()
             .map { LinearBrightness(it.brightness) }
-            .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+            .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null)
+            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
 
     override fun setTemporaryBrightness(value: LinearBrightness) {
         apiQueue.trySend(SetBrightnessMethod.Temporary(value))
@@ -183,4 +195,21 @@
         @JvmInline
         value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
     }
+
+    private fun logBrightnessChange(permanent: Boolean, value: Float) {
+        logBuffer.log(
+            LOG_BUFFER_BRIGHTNESS_CHANGE_TAG,
+            if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE,
+            { str1 = value.formatBrightness() },
+            { "Change requested: $str1" }
+        )
+    }
+
+    private companion object {
+        const val TABLE_COLUMN_BRIGHTNESS = "brightness"
+        const val TABLE_COLUMN_MIN = "min"
+        const val TABLE_COLUMN_MAX = "max"
+        const val TABLE_PREFIX_LINEAR = "linear"
+        const val LOG_BUFFER_BRIGHTNESS_CHANGE_TAG = "BrightnessChange"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
index 799a0a1..5647f521 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
@@ -17,12 +17,20 @@
 package com.android.systemui.brightness.domain.interactor
 
 import com.android.settingslib.display.BrightnessUtils
-import com.android.systemui.brightness.data.model.LinearBrightness
 import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.BrightnessLog
+import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.logDiffForTable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Converts between [GammaBrightness] and [LinearBrightness].
@@ -34,6 +42,8 @@
 @Inject
 constructor(
     private val screenBrightnessRepository: ScreenBrightnessRepository,
+    @Application private val applicationScope: CoroutineScope,
+    @BrightnessLog private val tableBuffer: TableLogBuffer,
 ) {
     /** Maximum value in the Gamma space for brightness */
     val maxGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX)
@@ -45,15 +55,17 @@
      * Brightness in the Gamma space for the current display. It will always represent a value
      * between [minGammaBrightness] and [maxGammaBrightness]
      */
-    val gammaBrightness =
+    val gammaBrightness: Flow<GammaBrightness> =
         with(screenBrightnessRepository) {
             combine(
-                linearBrightness,
-                minLinearBrightness,
-                maxLinearBrightness,
-            ) { brightness, min, max ->
-                brightness.toGammaBrightness(min, max)
-            }
+                    linearBrightness,
+                    minLinearBrightness,
+                    maxLinearBrightness,
+                ) { brightness, min, max ->
+                    brightness.toGammaBrightness(min, max)
+                }
+                .logDiffForTable(tableBuffer, TABLE_PREFIX_GAMMA, TABLE_COLUMN_BRIGHTNESS, null)
+                .stateIn(applicationScope, SharingStarted.WhileSubscribed(), GammaBrightness(0))
         }
 
     /** Sets the brightness temporarily, while the user is changing it. */
@@ -91,4 +103,9 @@
             BrightnessUtils.convertLinearToGammaFloat(floatValue, min.floatValue, max.floatValue)
         )
     }
+
+    private companion object {
+        const val TABLE_COLUMN_BRIGHTNESS = "brightness"
+        const val TABLE_PREFIX_GAMMA = "gamma"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
deleted file mode 100644
index e20d003..0000000
--- a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.brightness.shared
-
-import androidx.annotation.IntRange
-import com.android.settingslib.display.BrightnessUtils
-
-@JvmInline
-value class GammaBrightness(
-    @IntRange(
-        from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(),
-        to = BrightnessUtils.GAMMA_SPACE_MAX.toLong()
-    )
-    val value: Int
-)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
copy to packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt
index 0854e93..b514fef 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt
@@ -13,14 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.dreams.homecontrols
 
-import android.app.Activity
-import android.service.dreams.DreamService
-import javax.inject.Inject
+package com.android.systemui.brightness.shared.model
 
-class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
-    override fun getActivity(dreamService: DreamService): Activity {
-        return dreamService.activity
-    }
-}
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class BrightnessLog()
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt
new file mode 100644
index 0000000..7eba626
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.brightness.shared.model
+
+import androidx.annotation.IntRange
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+@JvmInline
+value class GammaBrightness(
+    @IntRange(
+        from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(),
+        to = BrightnessUtils.GAMMA_SPACE_MAX.toLong()
+    )
+    val value: Int
+)
+
+internal fun Flow<GammaBrightness>.logDiffForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: GammaBrightness?,
+): Flow<GammaBrightness> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(columnPrefix, columnName, initialValue?.value, isInitial = true)
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal: GammaBrightness?, newVal: GammaBrightness ->
+        if (prevVal != newVal) {
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal.value)
+        }
+        newVal
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt
new file mode 100644
index 0000000..1c886e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.brightness.shared.model
+
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+@JvmInline
+value class LinearBrightness(val floatValue: Float) {
+    fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness {
+        return if (floatValue < min.floatValue) {
+            min
+        } else if (floatValue > max.floatValue) {
+            max
+        } else {
+            this
+        }
+    }
+
+    val loggableString: String
+        get() = floatValue.formatBrightness()
+}
+
+fun Float.formatBrightness(): String {
+    return "%.3f".format(this)
+}
+
+internal fun Flow<LinearBrightness>.logDiffForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: LinearBrightness?,
+): Flow<LinearBrightness> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(
+            columnPrefix,
+            columnName,
+            initialValue?.loggableString,
+            isInitial = true
+        )
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal: LinearBrightness?, newVal: LinearBrightness
+        ->
+        if (prevVal != newVal) {
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal.loggableString)
+        }
+        newVal
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index a51d8ff..f991d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -33,14 +33,13 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformSlider
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
 import com.android.systemui.brightness.ui.viewmodel.Drag
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.utils.PolicyRestriction
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
 @Composable
@@ -107,8 +106,8 @@
     viewModel: BrightnessSliderViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val gamma: Int by
-        viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0)
+    val state by viewModel.currentBrightness.collectAsStateWithLifecycle()
+    val gamma = state.value
     val coroutineScope = rememberCoroutineScope()
     val restriction by
         viewModel.policyRestriction.collectAsStateWithLifecycle(
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
index f0988ba..16a1dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -18,14 +18,18 @@
 
 import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
 import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
 import com.android.systemui.utils.PolicyRestriction
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
 class BrightnessSliderViewModel
@@ -33,8 +37,14 @@
 constructor(
     private val screenBrightnessInteractor: ScreenBrightnessInteractor,
     private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor,
+    @Application private val applicationScope: CoroutineScope,
 ) {
-    val currentBrightness = screenBrightnessInteractor.gammaBrightness
+    val currentBrightness =
+        screenBrightnessInteractor.gammaBrightness.stateIn(
+            applicationScope,
+            SharingStarted.WhileSubscribed(),
+            GammaBrightness(0)
+        )
 
     val maxBrightness = screenBrightnessInteractor.maxGammaBrightness
     val minBrightness = screenBrightnessInteractor.minGammaBrightness
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
index 4dbb32d..1bbdfcd 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
@@ -19,16 +19,18 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 
 /**
- * Retrieves whether or not the device is docked according to DockManager. Emits a starting value
- *  of isDocked.
+ * Retrieves whether or not the device is docked according to DockManager. Emits a starting value of
+ * isDocked.
  */
 fun DockManager.retrieveIsDocked(): Flow<Boolean> =
     ConflatedCallbackFlow.conflatedCallbackFlow {
-        val callback = DockManager.DockEventListener { trySend(isDocked) }
-        addListener(callback)
-        trySend(isDocked)
+            val callback = DockManager.DockEventListener { trySend(isDocked) }
+            addListener(callback)
+            trySend(isDocked)
 
-        awaitClose { removeListener(callback) }
-    }
\ No newline at end of file
+            awaitClose { removeListener(callback) }
+        }
+        .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index b0d134f..f6ac7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -33,8 +33,8 @@
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.SystemDialogsCloser;
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
-import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
-import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
+import com.android.systemui.dreams.homecontrols.DreamServiceDelegate;
+import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl;
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.pipeline.shared.TileSpec;
@@ -202,8 +202,8 @@
     }
 
 
-    /** Provides activity for dream service */
+    /** Provides delegate to allow for testing of dream service */
     @Binds
-    DreamActivityProvider bindActivityProvider(DreamActivityProviderImpl impl);
+    DreamServiceDelegate bindDreamDelegate(DreamServiceDelegateImpl impl);
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
deleted file mode 100644
index b35b7f5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.dreams.homecontrols
-
-import android.app.Activity
-import android.service.dreams.DreamService
-
-fun interface DreamActivityProvider {
-    /**
-     * Provides abstraction for getting the activity associated with a dream service, so that the
-     * activity can be mocked in tests.
-     */
-    fun getActivity(dreamService: DreamService): Activity?
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt
new file mode 100644
index 0000000..2cfb02e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.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.dreams.homecontrols
+
+import android.app.Activity
+import android.service.dreams.DreamService
+
+/** Provides abstraction for [DreamService] methods, so they can be mocked in tests. */
+interface DreamServiceDelegate {
+    /** Wrapper for [DreamService.getActivity] which can be mocked in tests. */
+    fun getActivity(dreamService: DreamService): Activity?
+
+    /** Wrapper for [DreamService.wakeUp] which can be mocked in tests. */
+    fun wakeUp(dreamService: DreamService)
+
+    /** Wrapper for [DreamService.finish] which can be mocked in tests. */
+    fun finish(dreamService: DreamService)
+
+    /** Wrapper for [DreamService.getRedirectWake] which can be mocked in tests. */
+    fun redirectWake(dreamService: DreamService): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
index 0854e93..7dc5434 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
@@ -19,8 +19,20 @@
 import android.service.dreams.DreamService
 import javax.inject.Inject
 
-class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
+class DreamServiceDelegateImpl @Inject constructor() : DreamServiceDelegate {
     override fun getActivity(dreamService: DreamService): Activity {
         return dreamService.activity
     }
+
+    override fun finish(dreamService: DreamService) {
+        dreamService.finish()
+    }
+
+    override fun wakeUp(dreamService: DreamService) {
+        dreamService.wakeUp()
+    }
+
+    override fun redirectWake(dreamService: DreamService): Boolean {
+        return dreamService.redirectWake
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 76187c6..77c54ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.util.wakelock.WakeLock
 import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -46,7 +47,7 @@
     private val taskFragmentFactory: TaskFragmentComponent.Factory,
     private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
     private val wakeLockBuilder: WakeLock.Builder,
-    private val dreamActivityProvider: DreamActivityProvider,
+    private val dreamServiceDelegate: DreamServiceDelegate,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @DreamLog logBuffer: LogBuffer
 ) : DreamService() {
@@ -65,7 +66,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
-        val activity = dreamActivityProvider.getActivity(this)
+        val activity = dreamServiceDelegate.getActivity(this)
         if (activity == null) {
             finish()
             return
@@ -79,9 +80,9 @@
             taskFragmentFactory
                 .create(
                     activity = activity,
-                    onCreateCallback = this::onTaskFragmentCreated,
+                    onCreateCallback = { launchActivity() },
                     onInfoChangedCallback = this::onTaskFragmentInfoChanged,
-                    hide = { endDream() }
+                    hide = { endDream(false) }
                 )
                 .apply { createTaskFragment() }
 
@@ -91,16 +92,24 @@
     private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
         if (taskFragmentInfo.isEmpty) {
             logger.d("Finishing dream due to TaskFragment being empty")
-            endDream()
+            endDream(true)
         }
     }
 
-    private fun endDream() {
+    private fun endDream(handleRedirect: Boolean) {
         homeControlsComponentInteractor.onDreamEndUnexpectedly()
-        finish()
+        if (handleRedirect && dreamServiceDelegate.redirectWake(this)) {
+            dreamServiceDelegate.wakeUp(this)
+            serviceScope.launch {
+                delay(ACTIVITY_RESTART_DELAY)
+                launchActivity()
+            }
+        } else {
+            dreamServiceDelegate.finish(this)
+        }
     }
 
-    private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) {
+    private fun launchActivity() {
         val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
         val componentName = homeControlsComponentInteractor.panelComponent.value
         logger.d("Starting embedding $componentName")
@@ -134,6 +143,14 @@
          * complete.
          */
         val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+
+        /**
+         * Defines the delay after wakeup where we should attempt to restart the embedded activity.
+         * When a wakeup is redirected, the dream service may keep running. In this case, we should
+         * restart the activity if it finished. This delays ensures the activity is only restarted
+         * after the wakeup transition has played.
+         */
+        val ACTIVITY_RESTART_DELAY = 334.milliseconds
         const val TAG = "HomeControlsDreamService"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c084340..f4f8796 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -459,14 +459,6 @@
     @JvmField
     val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
 
-    /** Enable the Compose implementation of the PeopleSpaceActivity. */
-    @JvmField
-    val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space")
-
-    /** Enable the Compose implementation of the Quick Settings footer actions. */
-    @JvmField
-    val COMPOSE_QS_FOOTER_ACTIONS = releasedFlag("compose_qs_footer_actions")
-
     /** Enable the share wifi button in Quick Settings internet dialog. */
     @JvmField
     val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
new file mode 100644
index 0000000..52ccc21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -0,0 +1,413 @@
+/*
+ * 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.ui.composable
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.OpenInNew
+import androidx.compose.material.icons.filled.Accessibility
+import androidx.compose.material.icons.filled.Apps
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Keyboard
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationDrawerItemColors
+import androidx.compose.material3.NavigationDrawerItemDefaults
+import androidx.compose.material3.SearchBar
+import androidx.compose.material3.SearchBarDefaults
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.res.R
+
+@Composable
+fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () -> Unit) {
+    if (shouldUseSinglePane()) {
+        ShortcutHelperSinglePane(modifier, categories, onKeyboardSettingsClicked)
+    } else {
+        ShortcutHelperTwoPane(modifier, categories, onKeyboardSettingsClicked)
+    }
+}
+
+@Composable
+private fun shouldUseSinglePane() =
+    LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact
+
+@Composable
+private fun ShortcutHelperSinglePane(
+    modifier: Modifier = Modifier,
+    categories: List<ShortcutHelperCategory>,
+    onKeyboardSettingsClicked: () -> Unit,
+) {
+    Column(
+        modifier =
+            modifier
+                .fillMaxSize()
+                .verticalScroll(rememberScrollState())
+                .padding(start = 16.dp, end = 16.dp, top = 26.dp)
+    ) {
+        TitleBar()
+        Spacer(modifier = Modifier.height(6.dp))
+        ShortcutsSearchBar()
+        Spacer(modifier = Modifier.height(16.dp))
+        CategoriesPanelSinglePane(categories)
+        Spacer(modifier = Modifier.weight(1f))
+        KeyboardSettings(onClick = onKeyboardSettingsClicked)
+    }
+}
+
+@Composable
+private fun CategoriesPanelSinglePane(
+    categories: List<ShortcutHelperCategory>,
+) {
+    var expandedCategory by remember { mutableStateOf<ShortcutHelperCategory?>(null) }
+    Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+        categories.fastForEachIndexed { index, category ->
+            val isExpanded = expandedCategory == category
+            val itemShape =
+                if (index == 0) {
+                    ShortcutHelper.Shapes.singlePaneFirstCategory
+                } else if (index == categories.lastIndex) {
+                    ShortcutHelper.Shapes.singlePaneLastCategory
+                } else {
+                    ShortcutHelper.Shapes.singlePaneCategory
+                }
+            CategoryItemSinglePane(
+                category = category,
+                isExpanded = isExpanded,
+                onClick = {
+                    expandedCategory =
+                        if (isExpanded) {
+                            null
+                        } else {
+                            category
+                        }
+                },
+                shape = itemShape,
+            )
+        }
+    }
+}
+
+@Composable
+private fun CategoryItemSinglePane(
+    category: ShortcutHelperCategory,
+    isExpanded: Boolean,
+    onClick: () -> Unit,
+    shape: Shape,
+) {
+    Surface(
+        color = MaterialTheme.colorScheme.surfaceBright,
+        shape = shape,
+        onClick = onClick,
+    ) {
+        Column {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp)
+            ) {
+                Icon(category.icon, contentDescription = null)
+                Spacer(modifier = Modifier.width(16.dp))
+                Text(stringResource(category.labelResId))
+                Spacer(modifier = Modifier.weight(1f))
+                RotatingExpandCollapseIcon(isExpanded)
+            }
+            AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(category) }
+        }
+    }
+}
+
+@Composable
+private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
+    val expandIconRotationDegrees by
+        animateFloatAsState(
+            targetValue =
+                if (isExpanded) {
+                    180f
+                } else {
+                    0f
+                },
+            label = "Expand icon rotation animation"
+        )
+    Icon(
+        modifier =
+            Modifier.background(
+                    color = MaterialTheme.colorScheme.surfaceContainerHigh,
+                    shape = CircleShape
+                )
+                .graphicsLayer { rotationZ = expandIconRotationDegrees },
+        imageVector = Icons.Default.ExpandMore,
+        contentDescription =
+            if (isExpanded) {
+                stringResource(R.string.shortcut_helper_content_description_collapse_icon)
+            } else {
+                stringResource(R.string.shortcut_helper_content_description_expand_icon)
+            },
+        tint = MaterialTheme.colorScheme.onSurface
+    )
+}
+
+@Composable
+private fun ShortcutCategoryDetailsSinglePane(category: ShortcutHelperCategory) {
+    Box(modifier = Modifier.fillMaxWidth().heightIn(min = 300.dp)) {
+        Text(
+            modifier = Modifier.align(Alignment.Center),
+            text = stringResource(category.labelResId),
+        )
+    }
+}
+
+@Composable
+private fun ShortcutHelperTwoPane(
+    modifier: Modifier = Modifier,
+    categories: List<ShortcutHelperCategory>,
+    onKeyboardSettingsClicked: () -> Unit,
+) {
+    Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) {
+        TitleBar()
+        Spacer(modifier = Modifier.height(12.dp))
+        Row(Modifier.fillMaxWidth()) {
+            StartSidePanel(
+                modifier = Modifier.fillMaxWidth(fraction = 0.32f),
+                categories = categories,
+                onKeyboardSettingsClicked = onKeyboardSettingsClicked,
+            )
+            Spacer(modifier = Modifier.width(24.dp))
+            EndSidePanel(Modifier.fillMaxSize())
+        }
+    }
+}
+
+@Composable
+private fun StartSidePanel(
+    modifier: Modifier,
+    categories: List<ShortcutHelperCategory>,
+    onKeyboardSettingsClicked: () -> Unit,
+) {
+    Column(modifier) {
+        ShortcutsSearchBar()
+        Spacer(modifier = Modifier.heightIn(16.dp))
+        CategoriesPanelTwoPane(categories)
+        Spacer(modifier = Modifier.weight(1f))
+        KeyboardSettings(onKeyboardSettingsClicked)
+    }
+}
+
+@Composable
+private fun CategoriesPanelTwoPane(categories: List<ShortcutHelperCategory>) {
+    var selected by remember { mutableStateOf(categories.first()) }
+    Column {
+        categories.fastForEach {
+            CategoryItemTwoPane(
+                label = stringResource(it.labelResId),
+                icon = it.icon,
+                selected = selected == it,
+                onClick = { selected = it }
+            )
+        }
+    }
+}
+
+@Composable
+private fun CategoryItemTwoPane(
+    label: String,
+    icon: ImageVector,
+    selected: Boolean,
+    onClick: () -> Unit,
+    colors: NavigationDrawerItemColors =
+        NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
+) {
+    Surface(
+        selected = selected,
+        onClick = onClick,
+        modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 72.dp).fillMaxWidth(),
+        shape = RoundedCornerShape(28.dp),
+        color = colors.containerColor(selected).value,
+    ) {
+        Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
+            Icon(
+                modifier = Modifier.size(24.dp),
+                imageVector = icon,
+                contentDescription = null,
+                tint = colors.iconColor(selected).value
+            )
+            Spacer(Modifier.width(12.dp))
+            Box(Modifier.weight(1f)) {
+                Text(
+                    fontSize = 18.sp,
+                    color = colors.textColor(selected).value,
+                    style = MaterialTheme.typography.headlineSmall,
+                    text = label
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun EndSidePanel(modifier: Modifier) {
+    Surface(
+        modifier = modifier,
+        shape = RoundedCornerShape(28.dp),
+        color = MaterialTheme.colorScheme.surfaceBright
+    ) {}
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun TitleBar() {
+    CenterAlignedTopAppBar(
+        colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
+        title = {
+            Text(
+                text = stringResource(R.string.shortcut_helper_title),
+                color = MaterialTheme.colorScheme.onSurface,
+                style = MaterialTheme.typography.headlineSmall
+            )
+        }
+    )
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun ShortcutsSearchBar() {
+    var query by remember { mutableStateOf("") }
+    SearchBar(
+        colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
+        query = query,
+        active = false,
+        onActiveChange = {},
+        onQueryChange = { query = it },
+        onSearch = {},
+        leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
+        placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
+        content = {}
+    )
+}
+
+@Composable
+private fun KeyboardSettings(onClick: () -> Unit) {
+    Surface(
+        onClick = onClick,
+        shape = RoundedCornerShape(24.dp),
+        color = Color.Transparent,
+        modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth()
+    ) {
+        Row(
+            modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(
+                "Keyboard Settings",
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                fontSize = 16.sp
+            )
+            Spacer(modifier = Modifier.width(8.dp))
+            Icon(
+                imageVector = Icons.AutoMirrored.Default.OpenInNew,
+                contentDescription = null,
+                tint = MaterialTheme.colorScheme.onSurfaceVariant
+            )
+        }
+    }
+}
+
+/** Temporary data class just to populate the UI. */
+private data class ShortcutHelperCategory(
+    @StringRes val labelResId: Int,
+    val icon: ImageVector,
+)
+
+// Temporarily populating the categories directly in the UI.
+private val categories =
+    listOf(
+        ShortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv),
+        ShortcutHelperCategory(
+            R.string.shortcut_helper_category_multitasking,
+            Icons.Default.VerticalSplit
+        ),
+        ShortcutHelperCategory(R.string.shortcut_helper_category_input, Icons.Default.Keyboard),
+        ShortcutHelperCategory(R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps),
+        ShortcutHelperCategory(R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility),
+    )
+
+object ShortcutHelper {
+
+    object Shapes {
+        val singlePaneFirstCategory =
+            RoundedCornerShape(
+                topStart = Dimensions.SinglePaneCategoryCornerRadius,
+                topEnd = Dimensions.SinglePaneCategoryCornerRadius
+            )
+        val singlePaneLastCategory =
+            RoundedCornerShape(
+                bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
+                bottomEnd = Dimensions.SinglePaneCategoryCornerRadius
+            )
+        val singlePaneCategory = RectangleShape
+    }
+
+    object Dimensions {
+        val SinglePaneCategoryCornerRadius = 28.dp
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index ef4156d..1e8d239 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -23,9 +23,12 @@
 import androidx.activity.BackEventCompat
 import androidx.activity.ComponentActivity
 import androidx.activity.OnBackPressedCallback
+import androidx.compose.ui.platform.ComposeView
 import androidx.core.view.updatePadding
 import androidx.lifecycle.flowWithLifecycle
 import androidx.lifecycle.lifecycleScope
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
 import com.android.systemui.res.R
 import com.google.android.material.bottomsheet.BottomSheetBehavior
@@ -58,14 +61,30 @@
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_keyboard_shortcut_helper)
         setUpBottomSheetWidth()
+        expandBottomSheet()
         setUpInsets()
         setUpPredictiveBack()
         setUpSheetDismissListener()
         setUpDismissOnTouchOutside()
+        setUpComposeView()
         observeFinishRequired()
         viewModel.onViewOpened()
     }
 
+    private fun setUpComposeView() {
+        requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply {
+            setContent {
+                PlatformTheme {
+                    ShortcutHelper(
+                        onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
+                    )
+                }
+            }
+        }
+    }
+
+    private fun onKeyboardSettingsClicked() {}
+
     override fun onDestroy() {
         super.onDestroy()
         if (isFinishing) {
@@ -101,7 +120,8 @@
         bottomSheetContainer.setOnApplyWindowInsetsListener { _, insets ->
             val safeDrawingInsets = insets.safeDrawing
             // Make sure the bottom sheet is not covered by the status bar.
-            bottomSheetContainer.updatePadding(top = safeDrawingInsets.top)
+            bottomSheetBehavior.maxHeight =
+                resources.displayMetrics.heightPixels - safeDrawingInsets.top
             // Make sure the contents inside of the bottom sheet are not hidden by system bars, or
             // cutouts.
             bottomSheet.updatePadding(
@@ -171,7 +191,6 @@
 private val WindowInsets.safeDrawing
     get() =
         getInsets(WindowInsets.Type.systemBars())
-            .union(getInsets(WindowInsets.Type.ime()))
             .union(getInsets(WindowInsets.Type.displayCutout()))
 
 private fun Insets.union(insets: Insets): Insets = Insets.max(this, insets)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 9b07675f..756c6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -57,7 +57,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -70,6 +70,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 01109af..2a9ee9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -48,7 +48,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     val deviceEntryRepository: DeviceEntryRepository,
@@ -60,6 +60,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
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 7d3de30..f5e98f1 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
@@ -47,7 +47,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -60,6 +60,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 63294f7..47aa02a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -45,7 +45,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
@@ -56,6 +56,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7961b45..25c3b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -51,7 +51,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -63,6 +63,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index ca6ab3e..e516fa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -48,7 +48,7 @@
     @Main mainDispatcher: CoroutineDispatcher,
     @Background bgDispatcher: CoroutineDispatcher,
     private val glanceableHubTransitions: GlanceableHubTransitions,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     override val transitionRepository: KeyguardTransitionRepository,
     transitionInteractor: KeyguardTransitionInteractor,
     powerInteractor: PowerInteractor,
@@ -61,6 +61,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 8ca29c8..a540d76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -49,7 +49,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -64,6 +64,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index f1e98f3..8cab3cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -58,7 +58,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
     powerInteractor: PowerInteractor,
@@ -73,6 +73,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2603aab2..86d4cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -45,7 +45,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -57,6 +57,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 76a8223..19b2b81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -52,7 +52,7 @@
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
-    private val keyguardInteractor: KeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
     private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
@@ -67,6 +67,7 @@
         bgDispatcher = bgDispatcher,
         powerInteractor = powerInteractor,
         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        keyguardInteractor = keyguardInteractor,
     ) {
 
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ccce3bf..8ffa4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -105,33 +105,35 @@
         }
 
         return combine(
-            quickAffordanceAlwaysVisible(position),
-            keyguardInteractor.isDozing,
-            if (SceneContainerFlag.isEnabled) {
-                sceneInteractor
-                    .get()
-                    .transitionState
-                    .map {
-                        when (it) {
-                            is ObservableTransitionState.Idle ->
-                                it.currentScene == Scenes.Lockscreen
-                            is ObservableTransitionState.Transition ->
-                                it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen
+                quickAffordanceAlwaysVisible(position),
+                keyguardInteractor.isDozing,
+                if (SceneContainerFlag.isEnabled) {
+                    sceneInteractor
+                        .get()
+                        .transitionState
+                        .map {
+                            when (it) {
+                                is ObservableTransitionState.Idle ->
+                                    it.currentScene == Scenes.Lockscreen
+                                is ObservableTransitionState.Transition ->
+                                    it.fromScene == Scenes.Lockscreen ||
+                                        it.toScene == Scenes.Lockscreen
+                            }
                         }
-                    }
-                    .distinctUntilChanged()
-            } else {
-                keyguardInteractor.isKeyguardShowing
-            },
-            shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
-            biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
-                affordance
-            } else {
-                KeyguardQuickAffordanceModel.Hidden
+                        .distinctUntilChanged()
+                } else {
+                    keyguardInteractor.isKeyguardShowing
+                },
+                shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
+                biometricSettingsRepository.isCurrentUserInLockdown,
+            ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+                if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
+                    affordance
+                } else {
+                    KeyguardQuickAffordanceModel.Hidden
+                }
             }
-        }
+            .distinctUntilChanged()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 323ceef..e148207 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -53,6 +53,7 @@
     val bgDispatcher: CoroutineDispatcher,
     val powerInteractor: PowerInteractor,
     val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    val keyguardInteractor: KeyguardInteractor,
 ) {
     val name = this::class.simpleName ?: "UnknownTransitionInteractor"
     abstract val transitionRepository: KeyguardTransitionRepository
@@ -164,14 +165,10 @@
     @Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
     suspend fun maybeHandleInsecurePowerGesture(): Boolean {
         if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
-            if (transitionInteractor.getCurrentState() == KeyguardState.GONE) {
-                // If the current state is GONE when the launch gesture is triggered, it means we
-                // were in transition from GONE -> DOZING/AOD due to the first power button tap. The
-                // second tap indicates that the user's intent was actually to launch the unlocked
-                // (insecure) camera, so we should transition back to GONE.
+            if (keyguardInteractor.isKeyguardDismissible.value) {
                 startTransitionTo(
                     KeyguardState.GONE,
-                    ownerReason = "Power button gesture while GONE"
+                    ownerReason = "Power button gesture while keyguard is dismissible"
                 )
 
                 return true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 23c2491..807c322 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -65,7 +65,7 @@
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
         val disposableHandle =
             view.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
                     launch("$TAG#viewModel.alpha") {
                         // Do not independently apply alpha, as [KeyguardRootViewModel] should work
                         // for this and all its children
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index b9a79dc..1cf009d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,6 +30,7 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.view.LaunchableImageView
@@ -80,8 +81,8 @@
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
         val disposableHandle =
             view.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch("$TAG#viewModel.collect") {
                         viewModel.collect { buttonModel ->
                             updateButton(
                                 view = button,
@@ -93,7 +94,7 @@
                         }
                     }
 
-                    launch {
+                    launch("$TAG#updateButtonAlpha") {
                         updateButtonAlpha(
                             view = button,
                             viewModel = viewModel,
@@ -101,7 +102,7 @@
                         )
                     }
 
-                    launch {
+                    launch("$TAG#configurationBasedDimensions") {
                         configurationBasedDimensions.collect { dimensions ->
                             button.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
@@ -323,4 +324,6 @@
     private data class ConfigurationBasedDimensions(
         val buttonSizePx: Size,
     )
+
+    private const val TAG = "KeyguardQuickAffordanceViewBinder"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 8409f15..448a71c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -23,9 +23,12 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -42,6 +45,7 @@
     private val burnInInteractor: BurnInInteractor,
     private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
     configurationInteractor: ConfigurationInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
 
     /** Notifies when a new configuration is set */
@@ -69,12 +73,22 @@
                 .distinctUntilChanged()
         }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     private val burnIn: Flow<BurnInModel> =
-        burnInInteractor
-            .burnIn(
-                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
-                yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
-            )
+        combine(
+                burnInInteractor.burnIn(
+                    xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                    yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
+                ),
+                keyguardTransitionInteractor.transitionValue(KeyguardState.AOD),
+            ) { burnIn, aodTransitionValue ->
+                BurnInModel(
+                    (burnIn.translationX * aodTransitionValue).toInt(),
+                    (burnIn.translationY * aodTransitionValue).toInt(),
+                    burnIn.scale,
+                    burnIn.scaleClockOnly,
+                )
+            }
             .distinctUntilChanged()
 
     /** An observable for the x-offset by which the indication area should be translated. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index c4383fc..244d842 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -28,19 +29,23 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardQuickAffordancesCombinedViewModel
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     shadeInteractor: ShadeInteractor,
@@ -84,15 +89,20 @@
     /** The only time the expansion is important is while lockscreen is actively displayed */
     private val shadeExpansionAlpha =
         combine(
-            showingLockscreen,
-            shadeInteractor.anyExpansion,
-        ) { showingLockscreen, expansion ->
-            if (showingLockscreen) {
-                1 - expansion
-            } else {
-                0f
+                showingLockscreen,
+                shadeInteractor.anyExpansion,
+            ) { showingLockscreen, expansion ->
+                if (showingLockscreen) {
+                    1 - expansion
+                } else {
+                    0f
+                }
             }
-        }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Lazily,
+                initialValue = 0f,
+            )
 
     /**
      * ID of the slot that's currently selected in the preview that renders exclusively in the
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
index e0c5419..9c29bab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
@@ -43,6 +43,7 @@
     @IntoMap
     @ClassKey(MediaDataProcessor::class)
     fun bindMediaDataProcessor(interactor: MediaDataProcessor): CoreStartable
+
     companion object {
 
         @Provides
@@ -52,7 +53,7 @@
             newProvider: Provider<MediaCarouselInteractor>,
             mediaFlags: MediaFlags,
         ): MediaDataManager {
-            return if (mediaFlags.isMediaControlsRefactorEnabled()) {
+            return if (mediaFlags.isSceneContainerEnabled()) {
                 newProvider.get()
             } else {
                 legacyProvider.get()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index eed7752..8e985e1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -269,7 +269,7 @@
         }
 
     override fun start() {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (!mediaFlags.isSceneContainerEnabled()) {
             return
         }
 
@@ -746,8 +746,7 @@
             notif.extras.getParcelable(
                 Notification.EXTRA_BUILDER_APPLICATION_INFO,
                 ApplicationInfo::class.java
-            )
-                ?: getAppInfoFromPackage(sbn.packageName)
+            ) ?: getAppInfoFromPackage(sbn.packageName)
 
         // App name
         val appName = getAppName(sbn, appInfo)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 9e62300..b4bd4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -36,8 +36,8 @@
 import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
 import com.android.systemui.media.controls.shared.model.MediaCommonModel
-import com.android.systemui.media.controls.util.MediaControlsRefactorFlag
 import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -127,7 +127,7 @@
     val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
 
     override fun start() {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (!mediaFlags.isSceneContainerEnabled()) {
             return
         }
 
@@ -256,8 +256,6 @@
     companion object {
         val unsupported: Nothing
             get() =
-                error(
-                    "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled"
-                )
+                error("Code path not supported when ${SceneContainerFlag.DESCRIPTION} is enabled")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 19e3e07..8316b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -217,7 +217,7 @@
     private val animationScaleObserver: ContentObserver =
         object : ContentObserver(null) {
             override fun onChange(selfChange: Boolean) {
-                if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+                if (!mediaFlags.isSceneContainerEnabled()) {
                     MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
                 } else {
                     controllerByViewModel.values.forEach { it.updateAnimatorDurationScale() }
@@ -347,7 +347,7 @@
         inflateSettingsButton()
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
         configurationController.addCallback(configListener)
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (!mediaFlags.isSceneContainerEnabled()) {
             setUpListeners()
         } else {
             val visualStabilityCallback = OnReorderingAllowedListener {
@@ -389,7 +389,7 @@
                 listenForAnyStateToLockscreenTransition(this)
                 listenForLockscreenSettingChanges(this)
 
-                if (!mediaFlags.isMediaControlsRefactorEnabled()) return@repeatOnLifecycle
+                if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle
                 listenForMediaItemsChanges(this)
             }
         }
@@ -882,8 +882,7 @@
                     val previousVisibleIndex =
                         MediaPlayerData.playerKeys().indexOfFirst { key -> it == key }
                     mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex)
-                }
-                    ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
+                } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
             }
         } else if (isRtl && mediaContent.childCount > 0) {
             // In RTL, Scroll to the first player as it is the rightmost player in media carousel.
@@ -1092,7 +1091,7 @@
     }
 
     private fun updatePlayers(recreateMedia: Boolean) {
-        if (mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (mediaFlags.isSceneContainerEnabled()) {
             updateMediaPlayers(recreateMedia)
             return
         }
@@ -1192,7 +1191,7 @@
             currentStartLocation = startLocation
             currentEndLocation = endLocation
             currentTransitionProgress = progress
-            if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+            if (!mediaFlags.isSceneContainerEnabled()) {
                 for (mediaPlayer in MediaPlayerData.players()) {
                     updateViewControllerToState(mediaPlayer.mediaViewController, immediately)
                 }
@@ -1254,7 +1253,7 @@
 
     /** Update listening to seekbar. */
     private fun updateSeekbarListening(visibleToUser: Boolean) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (!mediaFlags.isSceneContainerEnabled()) {
             for (player in MediaPlayerData.players()) {
                 player.setListening(visibleToUser && currentlyExpanded)
             }
@@ -1269,7 +1268,7 @@
     private fun updateCarouselDimensions() {
         var width = 0
         var height = 0
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (!mediaFlags.isSceneContainerEnabled()) {
             for (mediaPlayer in MediaPlayerData.players()) {
                 val controller = mediaPlayer.mediaViewController
                 // When transitioning the view to gone, the view gets smaller, but the translation
@@ -1361,7 +1360,7 @@
                         !mediaManager.hasActiveMediaOrRecommendation() &&
                         desiredHostState.showsOnlyActiveMedia
 
-                if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+                if (!mediaFlags.isSceneContainerEnabled()) {
                     for (mediaPlayer in MediaPlayerData.players()) {
                         if (animate) {
                             mediaPlayer.mediaViewController.animatePendingStateChange(
@@ -1401,7 +1400,7 @@
         }
 
     fun closeGuts(immediate: Boolean = true) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (!mediaFlags.isSceneContainerEnabled()) {
             MediaPlayerData.players().forEach { it.closeGuts(immediate) }
         } else {
             controllerByViewModel.values.forEach { it.closeGuts(immediate) }
@@ -1544,7 +1543,7 @@
 
     @VisibleForTesting
     fun onSwipeToDismiss() {
-        if (mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (mediaFlags.isSceneContainerEnabled()) {
             mediaCarouselViewModel.onSwipeToDismiss()
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index a4f3e21..6589038 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.util.MediaFlags
@@ -61,6 +62,11 @@
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.launch
 
 private val TAG: String = MediaHierarchyManager::class.java.simpleName
@@ -89,6 +95,7 @@
  * This manager is responsible for placement of the unique media view between the different hosts
  * and animate the positions of the views to achieve seamless transitions.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class MediaHierarchyManager
 @Inject
@@ -101,6 +108,7 @@
     private val mediaManager: MediaDataManager,
     private val keyguardViewController: KeyguardViewController,
     private val dreamOverlayStateController: DreamOverlayStateController,
+    private val keyguardInteractor: KeyguardInteractor,
     communalTransitionViewModel: CommunalTransitionViewModel,
     configurationController: ConfigurationController,
     wakefulnessLifecycle: WakefulnessLifecycle,
@@ -236,6 +244,15 @@
 
     private var inSplitShade = false
 
+    /**
+     * Whether we are transitioning to the hub or from the hub to the shade. If so, use fade as the
+     * transformation type and skip calculating state with the bounds and the transition progress.
+     */
+    private val isHubTransition
+        get() =
+            desiredLocation == LOCATION_COMMUNAL_HUB ||
+                (previousLocation == LOCATION_COMMUNAL_HUB && desiredLocation == LOCATION_QS)
+
     /** Is there any active media or recommendation in the carousel? */
     private var hasActiveMediaOrRecommendation: Boolean = false
         get() = mediaManager.hasActiveMediaOrRecommendation()
@@ -413,6 +430,12 @@
     /** Is the communal UI showing */
     private var isCommunalShowing: Boolean = false
 
+    /** Is the communal UI showing and not dreaming */
+    private var onCommunalNotDreaming: Boolean = false
+
+    /** Is the communal UI showing, dreaming and shade expanding */
+    private var onCommunalDreamingAndShadeExpanding: Boolean = false
+
     /**
      * The current cross fade progress. 0.5f means it's just switching between the start and the end
      * location and the content is fully faded, while 0.75f means that we're halfway faded in again
@@ -585,11 +608,26 @@
 
         // Listen to the communal UI state. Make sure that communal UI is showing and hub itself is
         // available, ie. not disabled and able to be shown.
+        // When dreaming, qs expansion is immediately set to 1f, so we listen to shade expansion to
+        // calculate the new location.
         coroutineScope.launch {
-            communalTransitionViewModel.isUmoOnCommunal.collect { value ->
-                isCommunalShowing = value
-                updateDesiredLocation(forceNoAnimation = true)
-            }
+            combine(
+                    communalTransitionViewModel.isUmoOnCommunal,
+                    keyguardInteractor.isDreaming,
+                    // keep on communal before the shade is expanded enough to show the elements in
+                    // QS
+                    shadeInteractor.shadeExpansion
+                        .mapLatest { it < EXPANSION_THRESHOLD }
+                        .distinctUntilChanged(),
+                    ::Triple
+                )
+                .collectLatest { (communalShowing, isDreaming, isShadeExpanding) ->
+                    isCommunalShowing = communalShowing
+                    onCommunalDreamingAndShadeExpanding =
+                        communalShowing && isDreaming && isShadeExpanding
+                    onCommunalNotDreaming = communalShowing && !isDreaming
+                    updateDesiredLocation(forceNoAnimation = true)
+                }
         }
     }
 
@@ -805,6 +843,9 @@
         if (skipQqsOnExpansion) {
             return false
         }
+        if (isHubTransition) {
+            return false
+        }
         // This is an invalid transition, and can happen when using the camera gesture from the
         // lock screen. Disallow.
         if (
@@ -947,6 +988,9 @@
     @VisibleForTesting
     @TransformationType
     fun calculateTransformationType(): Int {
+        if (isHubTransition) {
+            return TRANSFORMATION_TYPE_FADE
+        }
         if (isTransitioningToFullShade) {
             if (inSplitShade && areGuidedTransitionHostsVisible()) {
                 return TRANSFORMATION_TYPE_TRANSITION
@@ -977,7 +1021,7 @@
      *   otherwise
      */
     private fun getTransformationProgress(): Float {
-        if (skipQqsOnExpansion) {
+        if (skipQqsOnExpansion || isHubTransition) {
             return -1.0f
         }
         val progress = getQSTransformationProgress()
@@ -1147,15 +1191,18 @@
         }
         val onLockscreen =
             (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
+
+        // UMO should show on hub unless the qs is expanding when not dreaming, or shade is
+        // expanding when dreaming
+        val onCommunal =
+            (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding
         val location =
             when {
                 mediaFlags.isSceneContainerEnabled() -> desiredLocation
                 dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
-
-                // UMO should show in communal unless the shade is expanding or visible.
-                isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
+                onCommunal -> LOCATION_COMMUNAL_HUB
                 (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
-                qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+                qsExpansion > EXPANSION_THRESHOLD && onLockscreen -> LOCATION_QS
                 onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
                 onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
 
@@ -1190,6 +1237,9 @@
             // reattach it without an animation
             return LOCATION_LOCKSCREEN
         }
+        // When communal showing while dreaming, skipQqsOnExpansion is also true but we want to
+        // return the calculated location, so it won't disappear as soon as shade is pulled down.
+        if (isCommunalShowing) return location
         if (skipQqsOnExpansion) {
             // When doing an immediate expand or collapse, we want to keep it in QS.
             return LOCATION_QS
@@ -1288,6 +1338,9 @@
          * transitioning
          */
         const val TRANSFORMATION_TYPE_FADE = 1
+
+        /** Expansion amount value at which elements start to become visible in the QS panel. */
+        const val EXPANSION_THRESHOLD = 0.4f
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 3837708..9d07232 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -203,7 +203,7 @@
     private val scrubbingChangeListener =
         object : SeekBarViewModel.ScrubbingChangeListener {
             override fun onScrubbingChanged(scrubbing: Boolean) {
-                if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+                if (!mediaFlags.isSceneContainerEnabled()) return
                 if (isScrubbing == scrubbing) return
                 isScrubbing = scrubbing
                 updateDisplayForScrubbingChange()
@@ -213,7 +213,7 @@
     private val enabledChangeListener =
         object : SeekBarViewModel.EnabledChangeListener {
             override fun onEnabledChanged(enabled: Boolean) {
-                if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+                if (!mediaFlags.isSceneContainerEnabled()) return
                 if (isSeekBarEnabled == enabled) return
                 isSeekBarEnabled = enabled
                 MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
@@ -229,7 +229,7 @@
      * @param listening True when player should be active. Otherwise, false.
      */
     fun setListening(listening: Boolean) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         seekBarViewModel.listening = listening
     }
 
@@ -263,7 +263,7 @@
                             )
                         )
                     }
-                    if (mediaFlags.isMediaControlsRefactorEnabled()) {
+                    if (mediaFlags.isSceneContainerEnabled()) {
                         if (
                             this@MediaViewController::recsConfigurationChangeListener.isInitialized
                         ) {
@@ -305,6 +305,7 @@
      */
     var collapsedLayout = ConstraintSet()
         @VisibleForTesting set
+
     /**
      * The expanded constraint set used to render a collapsed player. If it is modified, make sure
      * to call [refreshState]
@@ -334,7 +335,7 @@
      * Notify this controller that the view has been removed and all listeners should be destroyed
      */
     fun onDestroy() {
-        if (mediaFlags.isMediaControlsRefactorEnabled()) {
+        if (mediaFlags.isSceneContainerEnabled()) {
             if (this::seekBarObserver.isInitialized) {
                 seekBarViewModel.progress.removeObserver(seekBarObserver)
             }
@@ -657,7 +658,7 @@
         }
 
     fun attachPlayer(mediaViewHolder: MediaViewHolder) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         this.mediaViewHolder = mediaViewHolder
 
         // Setting up seek bar.
@@ -731,7 +732,7 @@
     }
 
     fun updateAnimatorDurationScale() {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         if (this::seekBarObserver.isInitialized) {
             seekBarObserver.animationEnabled =
                 globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
@@ -787,7 +788,7 @@
     }
 
     fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         this.recommendationViewHolder = recommendationViewHolder
 
         attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
@@ -796,13 +797,13 @@
     }
 
     fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         seekBarViewModel.logSeek = onSeek
         onBindSeekBar.invoke(seekBarViewModel)
     }
 
     fun setUpTurbulenceNoise() {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
             turbulenceNoiseAnimationConfig =
                 createTurbulenceNoiseConfig(
@@ -1153,13 +1154,13 @@
     }
 
     fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         isPrevButtonAvailable = isAvailable
         prevNotVisibleValue = notVisibleValue
     }
 
     fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
-        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!mediaFlags.isSceneContainerEnabled()) return
         isNextButtonAvailable = isAvailable
         nextNotVisibleValue = notVisibleValue
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt
deleted file mode 100644
index 2850b4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.media.controls.util
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the media_controls_refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object MediaControlsRefactorFlag {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_MEDIA_CONTROLS_REFACTOR
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the flag enabled? */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.mediaControlsRefactor()
-
-    /**
-     * 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/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 1e7bc0c..21c3111 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -52,8 +52,4 @@
 
     /** Check whether to use scene framework */
     fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
-
-    /** Check whether to use media refactor code */
-    fun isMediaControlsRefactorEnabled() =
-        MediaControlsRefactorFlag.isEnabled && SceneContainerFlag.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index a256b59..e931f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -18,10 +18,7 @@
 private const val TAG = "BackPanel"
 private const val DEBUG = false
 
-class BackPanel(
-        context: Context,
-        private val latencyTracker: LatencyTracker
-) : View(context) {
+class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
 
     var arrowsPointLeft = false
         set(value) {
@@ -42,39 +39,39 @@
     // True if the panel is currently on the left of the screen
     var isLeftPanel = false
 
-    /**
-     * Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw]
-     */
+    /** Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw] */
     private var trackingBackArrowLatency = false
 
-    /**
-     * The length of the arrow measured horizontally. Used for animating [arrowPath]
-     */
-    private var arrowLength = AnimatedFloat(
+    /** The length of the arrow measured horizontally. Used for animating [arrowPath] */
+    private var arrowLength =
+        AnimatedFloat(
             name = "arrowLength",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS
-    )
+        )
 
     /**
      * The height of the arrow measured vertically from its center to its top (i.e. half the total
      * height). Used for animating [arrowPath]
      */
-    var arrowHeight = AnimatedFloat(
+    var arrowHeight =
+        AnimatedFloat(
             name = "arrowHeight",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES
-    )
+        )
 
-    val backgroundWidth = AnimatedFloat(
+    val backgroundWidth =
+        AnimatedFloat(
             name = "backgroundWidth",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
             minimumValue = 0f,
-    )
+        )
 
-    val backgroundHeight = AnimatedFloat(
+    val backgroundHeight =
+        AnimatedFloat(
             name = "backgroundHeight",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
             minimumValue = 0f,
-    )
+        )
 
     /**
      * Corners of the background closer to the edge of the screen (where the arrow appeared from).
@@ -88,17 +85,19 @@
      */
     val backgroundFarCornerRadius = AnimatedFloat("backgroundFarCornerRadius")
 
-    var scale = AnimatedFloat(
+    var scale =
+        AnimatedFloat(
             name = "scale",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_SCALE,
             minimumValue = 0f
-    )
+        )
 
-    val scalePivotX = AnimatedFloat(
+    val scalePivotX =
+        AnimatedFloat(
             name = "scalePivotX",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
             minimumValue = backgroundWidth.pos / 2,
-    )
+        )
 
     /**
      * Left/right position of the background relative to the canvas. Also corresponds with the
@@ -107,21 +106,24 @@
      */
     var horizontalTranslation = AnimatedFloat(name = "horizontalTranslation")
 
-    var arrowAlpha = AnimatedFloat(
+    var arrowAlpha =
+        AnimatedFloat(
             name = "arrowAlpha",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
             minimumValue = 0f,
             maximumValue = 1f
-    )
+        )
 
-    val backgroundAlpha = AnimatedFloat(
+    val backgroundAlpha =
+        AnimatedFloat(
             name = "backgroundAlpha",
             minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
             minimumValue = 0f,
             maximumValue = 1f
-    )
+        )
 
-    private val allAnimatedFloat = setOf(
+    private val allAnimatedFloat =
+        setOf(
             arrowLength,
             arrowHeight,
             backgroundWidth,
@@ -132,7 +134,7 @@
             horizontalTranslation,
             arrowAlpha,
             backgroundAlpha
-    )
+        )
 
     /**
      * Canvas vertical translation. How far up/down the arrow and background appear relative to the
@@ -140,43 +142,45 @@
      */
     var verticalTranslation = AnimatedFloat("verticalTranslation")
 
-    /**
-     * Use for drawing debug info. Can only be set if [DEBUG]=true
-     */
+    /** Use for drawing debug info. Can only be set if [DEBUG]=true */
     var drawDebugInfo: ((canvas: Canvas) -> Unit)? = null
         set(value) {
             if (DEBUG) field = value
         }
 
     internal fun updateArrowPaint(arrowThickness: Float) {
-
         arrowPaint.strokeWidth = arrowThickness
 
-        val isDeviceInNightTheme = resources.configuration.uiMode and
-                Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+        val isDeviceInNightTheme =
+            resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+                Configuration.UI_MODE_NIGHT_YES
 
-        arrowPaint.color = Utils.getColorAttrDefaultColor(context,
+        arrowPaint.color =
+            Utils.getColorAttrDefaultColor(
+                context,
                 if (isDeviceInNightTheme) {
                     com.android.internal.R.attr.materialColorOnSecondaryContainer
                 } else {
                     com.android.internal.R.attr.materialColorOnSecondaryFixed
                 }
-        )
+            )
 
-        arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
+        arrowBackgroundPaint.color =
+            Utils.getColorAttrDefaultColor(
+                context,
                 if (isDeviceInNightTheme) {
                     com.android.internal.R.attr.materialColorSecondaryContainer
                 } else {
                     com.android.internal.R.attr.materialColorSecondaryFixedDim
                 }
-        )
+            )
     }
 
     inner class AnimatedFloat(
-            name: String,
-            private val minimumVisibleChange: Float? = null,
-            private val minimumValue: Float? = null,
-            private val maximumValue: Float? = null,
+        name: String,
+        private val minimumVisibleChange: Float? = null,
+        private val minimumValue: Float? = null,
+        private val maximumValue: Float? = null,
     ) {
 
         // The resting position when not stretched by a touch drag
@@ -207,19 +211,21 @@
         }
 
         init {
-            val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
-                override fun setValue(animatedFloat: AnimatedFloat, value: Float) {
-                    animatedFloat.pos = value
-                }
+            val floatProp =
+                object : FloatPropertyCompat<AnimatedFloat>(name) {
+                    override fun setValue(animatedFloat: AnimatedFloat, value: Float) {
+                        animatedFloat.pos = value
+                    }
 
-                override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
-            }
-            animation = SpringAnimation(this, floatProp).apply {
-                spring = SpringForce()
-                this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
-                this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
-                this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
-            }
+                    override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
+                }
+            animation =
+                SpringAnimation(this, floatProp).apply {
+                    spring = SpringForce()
+                    this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
+                    this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
+                    this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
+                }
         }
 
         fun snapTo(newPosition: Float) {
@@ -233,11 +239,10 @@
             snapTo(restingPosition)
         }
 
-
         fun stretchTo(
-                stretchAmount: Float,
-                startingVelocity: Float? = null,
-                springForce: SpringForce? = null
+            stretchAmount: Float,
+            startingVelocity: Float? = null,
+            springForce: SpringForce? = null
         ) {
             animation.apply {
                 startingVelocity?.let {
@@ -297,8 +302,8 @@
     }
 
     fun addAnimationEndListener(
-            animatedFloat: AnimatedFloat,
-            endListener: DelayedOnAnimationEndListener
+        animatedFloat: AnimatedFloat,
+        endListener: DelayedOnAnimationEndListener
     ): Boolean {
         return if (animatedFloat.isRunning) {
             animatedFloat.addEndListener(endListener)
@@ -314,51 +319,51 @@
     }
 
     fun setStretch(
-            horizontalTranslationStretchAmount: Float,
-            arrowStretchAmount: Float,
-            arrowAlphaStretchAmount: Float,
-            backgroundAlphaStretchAmount: Float,
-            backgroundWidthStretchAmount: Float,
-            backgroundHeightStretchAmount: Float,
-            edgeCornerStretchAmount: Float,
-            farCornerStretchAmount: Float,
-            fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
+        horizontalTranslationStretchAmount: Float,
+        arrowStretchAmount: Float,
+        arrowAlphaStretchAmount: Float,
+        backgroundAlphaStretchAmount: Float,
+        backgroundWidthStretchAmount: Float,
+        backgroundHeightStretchAmount: Float,
+        edgeCornerStretchAmount: Float,
+        farCornerStretchAmount: Float,
+        fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
     ) {
         horizontalTranslation.stretchBy(
-                finalPosition = fullyStretchedDimens.horizontalTranslation,
-                amount = horizontalTranslationStretchAmount
+            finalPosition = fullyStretchedDimens.horizontalTranslation,
+            amount = horizontalTranslationStretchAmount
         )
         arrowLength.stretchBy(
-                finalPosition = fullyStretchedDimens.arrowDimens.length,
-                amount = arrowStretchAmount
+            finalPosition = fullyStretchedDimens.arrowDimens.length,
+            amount = arrowStretchAmount
         )
         arrowHeight.stretchBy(
-                finalPosition = fullyStretchedDimens.arrowDimens.height,
-                amount = arrowStretchAmount
+            finalPosition = fullyStretchedDimens.arrowDimens.height,
+            amount = arrowStretchAmount
         )
         arrowAlpha.stretchBy(
-                finalPosition = fullyStretchedDimens.arrowDimens.alpha,
-                amount = arrowAlphaStretchAmount
+            finalPosition = fullyStretchedDimens.arrowDimens.alpha,
+            amount = arrowAlphaStretchAmount
         )
         backgroundAlpha.stretchBy(
-                finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
-                amount = backgroundAlphaStretchAmount
+            finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
+            amount = backgroundAlphaStretchAmount
         )
         backgroundWidth.stretchBy(
-                finalPosition = fullyStretchedDimens.backgroundDimens.width,
-                amount = backgroundWidthStretchAmount
+            finalPosition = fullyStretchedDimens.backgroundDimens.width,
+            amount = backgroundWidthStretchAmount
         )
         backgroundHeight.stretchBy(
-                finalPosition = fullyStretchedDimens.backgroundDimens.height,
-                amount = backgroundHeightStretchAmount
+            finalPosition = fullyStretchedDimens.backgroundDimens.height,
+            amount = backgroundHeightStretchAmount
         )
         backgroundEdgeCornerRadius.stretchBy(
-                finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
-                amount = edgeCornerStretchAmount
+            finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
+            amount = edgeCornerStretchAmount
         )
         backgroundFarCornerRadius.stretchBy(
-                finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
-                amount = farCornerStretchAmount
+            finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
+            amount = farCornerStretchAmount
         )
     }
 
@@ -373,8 +378,11 @@
     }
 
     fun popArrowAlpha(startingVelocity: Float, springForce: SpringForce? = null) {
-        arrowAlpha.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity,
-                springForce = springForce)
+        arrowAlpha.stretchTo(
+            stretchAmount = 0f,
+            startingVelocity = startingVelocity,
+            springForce = springForce
+        )
     }
 
     fun resetStretch() {
@@ -392,12 +400,10 @@
         backgroundFarCornerRadius.snapToRestingPosition()
     }
 
-    /**
-     * Updates resting arrow and background size not accounting for stretch
-     */
+    /** Updates resting arrow and background size not accounting for stretch */
     internal fun setRestingDimens(
-            restingParams: EdgePanelParams.BackIndicatorDimens,
-            animate: Boolean = true
+        restingParams: EdgePanelParams.BackIndicatorDimens,
+        animate: Boolean = true
     ) {
         horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
         scale.updateRestingPosition(restingParams.scale)
@@ -410,27 +416,29 @@
         backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
         backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
         backgroundEdgeCornerRadius.updateRestingPosition(
-                restingParams.backgroundDimens.edgeCornerRadius, animate
+            restingParams.backgroundDimens.edgeCornerRadius,
+            animate
         )
         backgroundFarCornerRadius.updateRestingPosition(
-                restingParams.backgroundDimens.farCornerRadius, animate
+            restingParams.backgroundDimens.farCornerRadius,
+            animate
         )
     }
 
     fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
 
     fun setSpring(
-            horizontalTranslation: SpringForce? = null,
-            verticalTranslation: SpringForce? = null,
-            scale: SpringForce? = null,
-            arrowLength: SpringForce? = null,
-            arrowHeight: SpringForce? = null,
-            arrowAlpha: SpringForce? = null,
-            backgroundAlpha: SpringForce? = null,
-            backgroundFarCornerRadius: SpringForce? = null,
-            backgroundEdgeCornerRadius: SpringForce? = null,
-            backgroundWidth: SpringForce? = null,
-            backgroundHeight: SpringForce? = null,
+        horizontalTranslation: SpringForce? = null,
+        verticalTranslation: SpringForce? = null,
+        scale: SpringForce? = null,
+        arrowLength: SpringForce? = null,
+        arrowHeight: SpringForce? = null,
+        arrowAlpha: SpringForce? = null,
+        backgroundAlpha: SpringForce? = null,
+        backgroundFarCornerRadius: SpringForce? = null,
+        backgroundEdgeCornerRadius: SpringForce? = null,
+        backgroundWidth: SpringForce? = null,
+        backgroundHeight: SpringForce? = null,
     ) {
         arrowLength?.let { this.arrowLength.spring = it }
         arrowHeight?.let { this.arrowHeight.spring = it }
@@ -459,26 +467,28 @@
 
         if (!isLeftPanel) canvas.scale(-1f, 1f, canvasWidth / 2.0f, 0f)
 
-        canvas.translate(
-                horizontalTranslation.pos,
-                height * 0.5f + verticalTranslation.pos
-        )
+        canvas.translate(horizontalTranslation.pos, height * 0.5f + verticalTranslation.pos)
 
         canvas.scale(scale.pos, scale.pos, scalePivotX, 0f)
 
-        val arrowBackground = arrowBackgroundRect.apply {
-            left = 0f
-            top = -halfHeight
-            right = backgroundWidth
-            bottom = halfHeight
-        }.toPathWithRoundCorners(
-                topLeft = edgeCorner,
-                bottomLeft = edgeCorner,
-                topRight = farCorner,
-                bottomRight = farCorner
+        val arrowBackground =
+            arrowBackgroundRect
+                .apply {
+                    left = 0f
+                    top = -halfHeight
+                    right = backgroundWidth
+                    bottom = halfHeight
+                }
+                .toPathWithRoundCorners(
+                    topLeft = edgeCorner,
+                    bottomLeft = edgeCorner,
+                    topRight = farCorner,
+                    bottomRight = farCorner
+                )
+        canvas.drawPath(
+            arrowBackground,
+            arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() }
         )
-        canvas.drawPath(arrowBackground,
-                arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() })
 
         val dx = arrowLength.pos
         val dy = arrowHeight.pos
@@ -487,8 +497,8 @@
         // either the tip or the back of the arrow, whichever is closer
         val arrowOffset = (backgroundWidth - dx) / 2
         canvas.translate(
-                /* dx= */ arrowOffset,
-                /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
+            /* dx= */ arrowOffset,
+            /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
         )
 
         val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
@@ -500,8 +510,8 @@
         }
 
         val arrowPath = calculateArrowPath(dx = dx, dy = dy)
-        val arrowPaint = arrowPaint
-                .apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
+        val arrowPaint =
+            arrowPaint.apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
         canvas.drawPath(arrowPath, arrowPaint)
         canvas.restore()
 
@@ -519,17 +529,23 @@
     }
 
     private fun RectF.toPathWithRoundCorners(
-            topLeft: Float = 0f,
-            topRight: Float = 0f,
-            bottomRight: Float = 0f,
-            bottomLeft: Float = 0f
-    ): Path = Path().apply {
-        val corners = floatArrayOf(
-                topLeft, topLeft,
-                topRight, topRight,
-                bottomRight, bottomRight,
-                bottomLeft, bottomLeft
-        )
-        addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
-    }
-}
\ No newline at end of file
+        topLeft: Float = 0f,
+        topRight: Float = 0f,
+        bottomRight: Float = 0f,
+        bottomLeft: Float = 0f
+    ): Path =
+        Path().apply {
+            val corners =
+                floatArrayOf(
+                    topLeft,
+                    topLeft,
+                    topRight,
+                    topRight,
+                    bottomRight,
+                    bottomRight,
+                    bottomLeft,
+                    bottomLeft
+                )
+            addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index f8086f5..18358a7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -27,7 +27,6 @@
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.VelocityTracker
-import android.view.View
 import android.view.ViewConfiguration
 import android.view.WindowManager
 import androidx.annotation.VisibleForTesting
@@ -37,11 +36,12 @@
 import com.android.internal.jank.Cuj
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.ViewController
+import com.android.systemui.util.concurrency.BackPanelUiThread
+import com.android.systemui.util.concurrency.UiThreadContext
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -85,11 +85,11 @@
     context: Context,
     private val windowManager: WindowManager,
     private val viewConfiguration: ViewConfiguration,
-    @Main private val mainHandler: Handler,
+    private val mainHandler: Handler,
     private val systemClock: SystemClock,
     private val vibratorHelper: VibratorHelper,
     private val configurationController: ConfigurationController,
-    private val latencyTracker: LatencyTracker,
+    latencyTracker: LatencyTracker,
     private val interactionJankMonitor: InteractionJankMonitor,
 ) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
 
@@ -104,7 +104,7 @@
     constructor(
         private val windowManager: WindowManager,
         private val viewConfiguration: ViewConfiguration,
-        @Main private val mainHandler: Handler,
+        @BackPanelUiThread private val uiThreadContext: UiThreadContext,
         private val systemClock: SystemClock,
         private val vibratorHelper: VibratorHelper,
         private val configurationController: ConfigurationController,
@@ -113,20 +113,19 @@
     ) {
         /** Construct a [BackPanelController]. */
         fun create(context: Context): BackPanelController {
-            val backPanelController =
-                BackPanelController(
+            uiThreadContext.isCurrentThread()
+            return BackPanelController(
                     context,
                     windowManager,
                     viewConfiguration,
-                    mainHandler,
+                    uiThreadContext.handler,
                     systemClock,
                     vibratorHelper,
                     configurationController,
                     latencyTracker,
                     interactionJankMonitor
                 )
-            backPanelController.init()
-            return backPanelController
+                .also { it.init() }
         }
     }
 
@@ -164,6 +163,7 @@
 
     private val elapsedTimeSinceInactive
         get() = systemClock.uptimeMillis() - gestureInactiveTime
+
     private val elapsedTimeSinceEntry
         get() = systemClock.uptimeMillis() - gestureEntryTime
 
@@ -612,6 +612,7 @@
     }
 
     private var previousPreThresholdWidthInterpolator = params.entryWidthInterpolator
+
     private fun preThresholdWidthStretchAmount(progress: Float): Float {
         val interpolator = run {
             val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop
@@ -677,8 +678,7 @@
             velocityTracker?.run {
                 computeCurrentVelocity(PX_PER_SEC)
                 xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
-            }
-                ?: 0f
+            } ?: 0f
         val isPastFlingVelocityThreshold =
             flingVelocity > viewConfiguration.scaledMinimumFlingVelocity
         return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
@@ -1006,15 +1006,15 @@
 
     private fun performDeactivatedHapticFeedback() {
         vibratorHelper.performHapticFeedback(
-                mView,
-                HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+            mView,
+            HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
         )
     }
 
     private fun performActivatedHapticFeedback() {
         vibratorHelper.performHapticFeedback(
-                mView,
-                HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+            mView,
+            HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
         )
     }
 
@@ -1028,8 +1028,7 @@
             velocityTracker?.run {
                 computeCurrentVelocity(PX_PER_MS)
                 MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity))
-            }
-                ?: valueOnFastVelocity
+            } ?: valueOnFastVelocity
 
         return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index d0f8412..2dc09e5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -44,8 +44,6 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.icu.text.SimpleDateFormat;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -55,7 +53,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
-import android.view.Choreographer;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindowManager;
 import android.view.InputDevice;
@@ -75,7 +72,6 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
@@ -94,7 +90,8 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.BackPanelUiThread;
+import com.android.systemui.util.concurrency.UiThreadContext;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.pip.Pip;
@@ -136,7 +133,7 @@
                 public void onSystemGestureExclusionChanged(int displayId,
                         Region systemGestureExclusion, Region unrestrictedOrNull) {
                     if (displayId == mDisplayId) {
-                        mMainExecutor.execute(() -> {
+                        mUiThreadContext.getExecutor().execute(() -> {
                             mExcludeRegion.set(systemGestureExclusion);
                             mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null
                                     ? unrestrictedOrNull : systemGestureExclusion);
@@ -215,8 +212,7 @@
     private final Point mDisplaySize = new Point();
     private final int mDisplayId;
 
-    private final Executor mMainExecutor;
-    private final Handler mMainHandler;
+    private final UiThreadContext mUiThreadContext;
     private final Executor mBackgroundExecutor;
 
     private final Rect mPipExcludedBounds = new Rect();
@@ -411,8 +407,7 @@
             OverviewProxyService overviewProxyService,
             SysUiState sysUiState,
             PluginManager pluginManager,
-            @Main Executor executor,
-            @Main Handler handler,
+            @BackPanelUiThread UiThreadContext uiThreadContext,
             @Background Executor backgroundExecutor,
             UserTracker userTracker,
             NavigationModeController navigationModeController,
@@ -428,8 +423,7 @@
             Provider<LightBarController> lightBarControllerProvider) {
         mContext = context;
         mDisplayId = context.getDisplayId();
-        mMainExecutor = executor;
-        mMainHandler = handler;
+        mUiThreadContext = uiThreadContext;
         mBackgroundExecutor = backgroundExecutor;
         mUserTracker = userTracker;
         mOverviewProxyService = overviewProxyService;
@@ -478,7 +472,7 @@
                 ViewConfiguration.getLongPressTimeout());
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mMainHandler, mContext, this::onNavigationSettingsChanged);
+                mUiThreadContext.getHandler(), mContext, this::onNavigationSettingsChanged);
 
         updateCurrentUserResources();
     }
@@ -564,13 +558,15 @@
         mIsAttached = true;
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
-        mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
-        int [] inputDevices = mInputManager.getInputDeviceIds();
+        mInputManager.registerInputDeviceListener(
+                mInputDeviceListener,
+                mUiThreadContext.getHandler());
+        int[] inputDevices = mInputManager.getInputDeviceIds();
         for (int inputDeviceId : inputDevices) {
             mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
         }
         updateIsEnabled();
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, mUiThreadContext.getExecutor());
     }
 
     /**
@@ -617,6 +613,10 @@
     }
 
     private void updateIsEnabled() {
+        mUiThreadContext.runWithScissors(this::updateIsEnabledInner);
+    }
+
+    private void updateIsEnabledInner() {
         try {
             Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
 
@@ -661,12 +661,12 @@
                 TaskStackChangeListeners.getInstance().registerTaskStackListener(
                         mTaskStackListener);
                 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                        mMainExecutor::execute, mOnPropertiesChangedListener);
+                        mUiThreadContext.getExecutor()::execute, mOnPropertiesChangedListener);
                 mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(
                         mOnIsInPipStateChangedListener));
                 mDesktopModeOptional.ifPresent(
                         dm -> dm.addDesktopGestureExclusionRegionListener(
-                                mDesktopCornersChangedListener, mMainExecutor));
+                                mDesktopCornersChangedListener, mUiThreadContext.getExecutor()));
 
                 try {
                     mWindowManagerService.registerSystemGestureExclusionListener(
@@ -677,8 +677,8 @@
 
                 // Register input event receiver
                 mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
-                mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
-                        Choreographer.getInstance(), this::onInputEvent);
+                mInputEventReceiver = mInputMonitor.getInputReceiver(mUiThreadContext.getLooper(),
+                        mUiThreadContext.getChoreographer(), this::onInputEvent);
 
                 // Add a nav bar panel window
                 resetEdgeBackPlugin();
@@ -773,7 +773,7 @@
         mUseMLModel = newState;
 
         if (mUseMLModel) {
-            Assert.isMainThread();
+            mUiThreadContext.isCurrentThread();
             if (mMLModelIsLoading) {
                 Log.d(TAG, "Model tried to load while already loading.");
                 return;
@@ -804,12 +804,13 @@
         }
         BackGestureTfClassifierProvider finalProvider = provider;
         Map<String, Integer> finalVocab = vocab;
-        mMainExecutor.execute(() -> onMLModelLoadFinished(finalProvider, finalVocab, threshold));
+        mUiThreadContext.getExecutor().execute(
+                () -> onMLModelLoadFinished(finalProvider, finalVocab, threshold));
     }
 
     private void onMLModelLoadFinished(BackGestureTfClassifierProvider provider,
             Map<String, Integer> vocab, float threshold) {
-        Assert.isMainThread();
+        mUiThreadContext.isCurrentThread();
         mMLModelIsLoading = false;
         if (!mUseMLModel) {
             // This can happen if the user disables Gesture Nav while the model is loading.
@@ -1291,7 +1292,7 @@
         updateBackAnimationThresholds();
         if (mLightBarControllerProvider.get() != null) {
             mBackAnimation.setStatusBarCustomizer((appearance) -> {
-                mMainExecutor.execute(() ->
+                mUiThreadContext.getExecutor().execute(() ->
                         mLightBarControllerProvider.get()
                                 .customizeStatusBarAppearance(appearance));
             });
@@ -1308,8 +1309,7 @@
         private final OverviewProxyService mOverviewProxyService;
         private final SysUiState mSysUiState;
         private final PluginManager mPluginManager;
-        private final Executor mExecutor;
-        private final Handler mHandler;
+        private final UiThreadContext mUiThreadContext;
         private final Executor mBackgroundExecutor;
         private final UserTracker mUserTracker;
         private final NavigationModeController mNavigationModeController;
@@ -1327,29 +1327,27 @@
 
         @Inject
         public Factory(OverviewProxyService overviewProxyService,
-                       SysUiState sysUiState,
-                       PluginManager pluginManager,
-                       @Main Executor executor,
-                       @Main Handler handler,
-                       @Background Executor backgroundExecutor,
-                       UserTracker userTracker,
-                       NavigationModeController navigationModeController,
-                       BackPanelController.Factory backPanelControllerFactory,
-                       ViewConfiguration viewConfiguration,
-                       WindowManager windowManager,
-                       IWindowManager windowManagerService,
-                       InputManager inputManager,
-                       Optional<Pip> pipOptional,
-                       Optional<DesktopMode> desktopModeOptional,
-                       FalsingManager falsingManager,
-                       Provider<BackGestureTfClassifierProvider>
-                               backGestureTfClassifierProviderProvider,
-                       Provider<LightBarController> lightBarControllerProvider) {
+                        SysUiState sysUiState,
+                        PluginManager pluginManager,
+                        @BackPanelUiThread UiThreadContext uiThreadContext,
+                        @Background Executor backgroundExecutor,
+                        UserTracker userTracker,
+                        NavigationModeController navigationModeController,
+                        BackPanelController.Factory backPanelControllerFactory,
+                        ViewConfiguration viewConfiguration,
+                        WindowManager windowManager,
+                        IWindowManager windowManagerService,
+                        InputManager inputManager,
+                        Optional<Pip> pipOptional,
+                        Optional<DesktopMode> desktopModeOptional,
+                        FalsingManager falsingManager,
+                        Provider<BackGestureTfClassifierProvider>
+                                backGestureTfClassifierProviderProvider,
+                        Provider<LightBarController> lightBarControllerProvider) {
             mOverviewProxyService = overviewProxyService;
             mSysUiState = sysUiState;
             mPluginManager = pluginManager;
-            mExecutor = executor;
-            mHandler = handler;
+            mUiThreadContext = uiThreadContext;
             mBackgroundExecutor = backgroundExecutor;
             mUserTracker = userTracker;
             mNavigationModeController = navigationModeController;
@@ -1367,26 +1365,26 @@
 
         /** Construct a {@link EdgeBackGestureHandler}. */
         public EdgeBackGestureHandler create(Context context) {
-            return new EdgeBackGestureHandler(
-                    context,
-                    mOverviewProxyService,
-                    mSysUiState,
-                    mPluginManager,
-                    mExecutor,
-                    mHandler,
-                    mBackgroundExecutor,
-                    mUserTracker,
-                    mNavigationModeController,
-                    mBackPanelControllerFactory,
-                    mViewConfiguration,
-                    mWindowManager,
-                    mWindowManagerService,
-                    mInputManager,
-                    mPipOptional,
-                    mDesktopModeOptional,
-                    mFalsingManager,
-                    mBackGestureTfClassifierProviderProvider,
-                    mLightBarControllerProvider);
+            return mUiThreadContext.runWithScissors(
+                    () -> new EdgeBackGestureHandler(
+                            context,
+                            mOverviewProxyService,
+                            mSysUiState,
+                            mPluginManager,
+                            mUiThreadContext,
+                            mBackgroundExecutor,
+                            mUserTracker,
+                            mNavigationModeController,
+                            mBackPanelControllerFactory,
+                            mViewConfiguration,
+                            mWindowManager,
+                            mWindowManagerService,
+                            mInputManager,
+                            mPipOptional,
+                            mDesktopModeOptional,
+                            mFalsingManager,
+                            mBackGestureTfClassifierProviderProvider,
+                            mLightBarControllerProvider));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 439b7e1..db8749f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -10,92 +10,114 @@
 data class EdgePanelParams(private var resources: Resources) {
 
     data class ArrowDimens(
-            val length: Float? = 0f,
-            val height: Float? = 0f,
-            val alpha: Float = 0f,
-            val heightSpring: SpringForce? = null,
-            val lengthSpring: SpringForce? = null,
-            var alphaSpring: Step<SpringForce>? = null,
-            var alphaInterpolator: Step<Float>? = null
+        val length: Float? = 0f,
+        val height: Float? = 0f,
+        val alpha: Float = 0f,
+        val heightSpring: SpringForce? = null,
+        val lengthSpring: SpringForce? = null,
+        var alphaSpring: Step<SpringForce>? = null,
+        var alphaInterpolator: Step<Float>? = null
     )
 
     data class BackgroundDimens(
-            val width: Float? = 0f,
-            val height: Float = 0f,
-            val edgeCornerRadius: Float = 0f,
-            val farCornerRadius: Float = 0f,
-            val alpha: Float = 0f,
-            val widthSpring: SpringForce? = null,
-            val heightSpring: SpringForce? = null,
-            val farCornerRadiusSpring: SpringForce? = null,
-            val edgeCornerRadiusSpring: SpringForce? = null,
-            val alphaSpring: SpringForce? = null,
+        val width: Float? = 0f,
+        val height: Float = 0f,
+        val edgeCornerRadius: Float = 0f,
+        val farCornerRadius: Float = 0f,
+        val alpha: Float = 0f,
+        val widthSpring: SpringForce? = null,
+        val heightSpring: SpringForce? = null,
+        val farCornerRadiusSpring: SpringForce? = null,
+        val edgeCornerRadiusSpring: SpringForce? = null,
+        val alphaSpring: SpringForce? = null,
     )
 
     data class BackIndicatorDimens(
-            val horizontalTranslation: Float? = 0f,
-            val scale: Float = 0f,
-            val scalePivotX: Float? = null,
-            val arrowDimens: ArrowDimens,
-            val backgroundDimens: BackgroundDimens,
-            val verticalTranslationSpring: SpringForce? = null,
-            val horizontalTranslationSpring: SpringForce? = null,
-            val scaleSpring: SpringForce? = null,
+        val horizontalTranslation: Float? = 0f,
+        val scale: Float = 0f,
+        val scalePivotX: Float? = null,
+        val arrowDimens: ArrowDimens,
+        val backgroundDimens: BackgroundDimens,
+        val verticalTranslationSpring: SpringForce? = null,
+        val horizontalTranslationSpring: SpringForce? = null,
+        val scaleSpring: SpringForce? = null,
     )
 
     lateinit var entryIndicator: BackIndicatorDimens
         private set
+
     lateinit var activeIndicator: BackIndicatorDimens
         private set
+
     lateinit var cancelledIndicator: BackIndicatorDimens
         private set
+
     lateinit var flungIndicator: BackIndicatorDimens
         private set
+
     lateinit var committedIndicator: BackIndicatorDimens
         private set
+
     lateinit var preThresholdIndicator: BackIndicatorDimens
         private set
+
     lateinit var fullyStretchedIndicator: BackIndicatorDimens
         private set
 
     // navigation bar edge constants
     var arrowPaddingEnd: Int = 0
         private set
+
     var arrowThickness: Float = 0f
         private set
+
     // The closest to y
     var minArrowYPosition: Int = 0
         private set
+
     var fingerOffset: Int = 0
         private set
+
     var staticTriggerThreshold: Float = 0f
         private set
+
     var reactivationTriggerThreshold: Float = 0f
         private set
+
     var deactivationTriggerThreshold: Float = 0f
         get() = -field
         private set
+
     lateinit var dynamicTriggerThresholdRange: ClosedRange<Float>
         private set
+
     var swipeProgressThreshold: Float = 0f
         private set
 
     lateinit var entryWidthInterpolator: Interpolator
         private set
+
     lateinit var entryWidthTowardsEdgeInterpolator: Interpolator
         private set
+
     lateinit var activeWidthInterpolator: Interpolator
         private set
+
     lateinit var arrowAngleInterpolator: Interpolator
         private set
+
     lateinit var horizontalTranslationInterpolator: Interpolator
         private set
+
     lateinit var verticalTranslationInterpolator: Interpolator
         private set
+
     lateinit var farCornerInterpolator: Interpolator
         private set
+
     lateinit var edgeCornerInterpolator: Interpolator
         private set
+
     lateinit var heightInterpolator: Interpolator
         private set
 
@@ -108,7 +130,10 @@
     }
 
     private fun getDimenFloat(id: Int): Float {
-        return TypedValue().run { resources.getValue(id, this, true); float }
+        return TypedValue().run {
+            resources.getValue(id, this, true)
+            float
+        }
     }
 
     private fun getPx(id: Int): Int {
@@ -123,11 +148,10 @@
         fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
         staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
         reactivationTriggerThreshold =
-                getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
+            getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
         deactivationTriggerThreshold =
-                getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
-        dynamicTriggerThresholdRange =
-                reactivationTriggerThreshold..deactivationTriggerThreshold
+            getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
+        dynamicTriggerThresholdRange = reactivationTriggerThreshold..deactivationTriggerThreshold
         swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
 
         entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
@@ -149,27 +173,31 @@
 
         val commonArrowDimensAlphaThreshold = .165f
         val commonArrowDimensAlphaFactor = 1.05f
-        val commonArrowDimensAlphaSpring = Step(
-            threshold = commonArrowDimensAlphaThreshold,
-            factor = commonArrowDimensAlphaFactor,
-            postThreshold = createSpring(180f, 0.9f),
-            preThreshold = createSpring(2000f, 0.6f)
-        )
-        val commonArrowDimensAlphaSpringInterpolator = Step(
-            threshold = commonArrowDimensAlphaThreshold,
-            factor = commonArrowDimensAlphaFactor,
-            postThreshold = 1f,
-            preThreshold = 0f
-        )
+        val commonArrowDimensAlphaSpring =
+            Step(
+                threshold = commonArrowDimensAlphaThreshold,
+                factor = commonArrowDimensAlphaFactor,
+                postThreshold = createSpring(180f, 0.9f),
+                preThreshold = createSpring(2000f, 0.6f)
+            )
+        val commonArrowDimensAlphaSpringInterpolator =
+            Step(
+                threshold = commonArrowDimensAlphaThreshold,
+                factor = commonArrowDimensAlphaFactor,
+                postThreshold = 1f,
+                preThreshold = 0f
+            )
 
-        entryIndicator = BackIndicatorDimens(
+        entryIndicator =
+            BackIndicatorDimens(
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
                 scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
                 horizontalTranslationSpring = createSpring(800f, 0.76f),
                 verticalTranslationSpring = createSpring(30000f, 1f),
                 scaleSpring = createSpring(120f, 0.8f),
-                arrowDimens = ArrowDimens(
+                arrowDimens =
+                    ArrowDimens(
                         length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
                         height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
                         alpha = 0f,
@@ -177,8 +205,9 @@
                         heightSpring = createSpring(600f, 0.4f),
                         alphaSpring = commonArrowDimensAlphaSpring,
                         alphaInterpolator = commonArrowDimensAlphaSpringInterpolator
-                ),
-                backgroundDimens = BackgroundDimens(
+                    ),
+                backgroundDimens =
+                    BackgroundDimens(
                         alpha = 1f,
                         width = getDimen(R.dimen.navigation_edge_entry_background_width),
                         height = getDimen(R.dimen.navigation_edge_entry_background_height),
@@ -188,16 +217,18 @@
                         heightSpring = createSpring(1500f, 0.45f),
                         farCornerRadiusSpring = createSpring(300f, 0.5f),
                         edgeCornerRadiusSpring = createSpring(150f, 0.5f),
-                )
-        )
+                    )
+            )
 
-        activeIndicator = BackIndicatorDimens(
+        activeIndicator =
+            BackIndicatorDimens(
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
                 horizontalTranslationSpring = createSpring(1000f, 0.8f),
                 scaleSpring = createSpring(325f, 0.55f),
                 scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width),
-                arrowDimens = ArrowDimens(
+                arrowDimens =
+                    ArrowDimens(
                         length = getDimen(R.dimen.navigation_edge_active_arrow_length),
                         height = getDimen(R.dimen.navigation_edge_active_arrow_height),
                         alpha = 1f,
@@ -205,8 +236,9 @@
                         heightSpring = activeCommittedArrowHeightSpring,
                         alphaSpring = commonArrowDimensAlphaSpring,
                         alphaInterpolator = commonArrowDimensAlphaSpringInterpolator
-                ),
-                backgroundDimens = BackgroundDimens(
+                    ),
+                backgroundDimens =
+                    BackgroundDimens(
                         alpha = 1f,
                         width = getDimen(R.dimen.navigation_edge_active_background_width),
                         height = getDimen(R.dimen.navigation_edge_active_background_height),
@@ -216,16 +248,18 @@
                         heightSpring = createSpring(10000f, 1f),
                         edgeCornerRadiusSpring = createSpring(2600f, 0.855f),
                         farCornerRadiusSpring = createSpring(1200f, 0.30f),
-                )
-        )
+                    )
+            )
 
-        preThresholdIndicator = BackIndicatorDimens(
+        preThresholdIndicator =
+            BackIndicatorDimens(
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_pre_threshold_scale),
                 scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
                 scaleSpring = createSpring(120f, 0.8f),
                 horizontalTranslationSpring = createSpring(6000f, 1f),
-                arrowDimens = ArrowDimens(
+                arrowDimens =
+                    ArrowDimens(
                         length = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_length),
                         height = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_height),
                         alpha = 1f,
@@ -233,32 +267,36 @@
                         heightSpring = createSpring(100f, 0.6f),
                         alphaSpring = commonArrowDimensAlphaSpring,
                         alphaInterpolator = commonArrowDimensAlphaSpringInterpolator
-                ),
-                backgroundDimens = BackgroundDimens(
+                    ),
+                backgroundDimens =
+                    BackgroundDimens(
                         alpha = 1f,
                         width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
                         height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
                         edgeCornerRadius =
-                                getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
+                            getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
                         farCornerRadius =
-                                getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
+                            getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
                         widthSpring = createSpring(650f, 1f),
                         heightSpring = createSpring(1500f, 0.45f),
                         farCornerRadiusSpring = createSpring(300f, 1f),
                         edgeCornerRadiusSpring = createSpring(250f, 0.5f),
-                )
-        )
+                    )
+            )
 
-        committedIndicator = activeIndicator.copy(
+        committedIndicator =
+            activeIndicator.copy(
                 horizontalTranslation = null,
                 scalePivotX = null,
-                arrowDimens = activeIndicator.arrowDimens.copy(
+                arrowDimens =
+                    activeIndicator.arrowDimens.copy(
                         lengthSpring = activeCommittedArrowLengthSpring,
                         heightSpring = activeCommittedArrowHeightSpring,
                         length = null,
                         height = null,
-                ),
-                backgroundDimens = activeIndicator.backgroundDimens.copy(
+                    ),
+                backgroundDimens =
+                    activeIndicator.backgroundDimens.copy(
                         alpha = 0f,
                         // explicitly set to null to preserve previous width upon state change
                         width = null,
@@ -267,49 +305,57 @@
                         edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
                         farCornerRadiusSpring = flungCommittedFarCornerSpring,
                         alphaSpring = createSpring(1400f, 1f),
-                ),
+                    ),
                 scale = 0.86f,
                 scaleSpring = createSpring(5700f, 1f),
-        )
+            )
 
-        flungIndicator = committedIndicator.copy(
-                arrowDimens = committedIndicator.arrowDimens.copy(
+        flungIndicator =
+            committedIndicator.copy(
+                arrowDimens =
+                    committedIndicator.arrowDimens.copy(
                         lengthSpring = createSpring(850f, 0.46f),
                         heightSpring = createSpring(850f, 0.46f),
                         length = activeIndicator.arrowDimens.length,
                         height = activeIndicator.arrowDimens.height
-                ),
-                backgroundDimens = committedIndicator.backgroundDimens.copy(
+                    ),
+                backgroundDimens =
+                    committedIndicator.backgroundDimens.copy(
                         widthSpring = flungCommittedWidthSpring,
                         heightSpring = flungCommittedHeightSpring,
                         edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
                         farCornerRadiusSpring = flungCommittedFarCornerSpring,
-                )
-        )
+                    )
+            )
 
-        cancelledIndicator = entryIndicator.copy(
-                backgroundDimens = entryIndicator.backgroundDimens.copy(
+        cancelledIndicator =
+            entryIndicator.copy(
+                backgroundDimens =
+                    entryIndicator.backgroundDimens.copy(
                         width = 0f,
                         alpha = 0f,
                         alphaSpring = createSpring(450f, 1f)
-                )
-        )
+                    )
+            )
 
-        fullyStretchedIndicator = BackIndicatorDimens(
+        fullyStretchedIndicator =
+            BackIndicatorDimens(
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_stretch_scale),
                 horizontalTranslationSpring = null,
                 verticalTranslationSpring = null,
                 scaleSpring = null,
-                arrowDimens = ArrowDimens(
+                arrowDimens =
+                    ArrowDimens(
                         length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
                         height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
                         alpha = 1f,
                         alphaSpring = null,
                         heightSpring = null,
                         lengthSpring = null,
-                ),
-                backgroundDimens = BackgroundDimens(
+                    ),
+                backgroundDimens =
+                    BackgroundDimens(
                         alpha = 1f,
                         width = getDimen(R.dimen.navigation_edge_stretch_background_width),
                         height = getDimen(R.dimen.navigation_edge_stretch_background_height),
@@ -320,11 +366,11 @@
                         heightSpring = null,
                         edgeCornerRadiusSpring = null,
                         farCornerRadiusSpring = null,
-                )
-        )
+                    )
+            )
     }
 }
 
 fun createSpring(stiffness: Float, dampingRatio: Float): SpringForce {
     return SpringForce().setStiffness(stiffness).setDampingRatio(dampingRatio)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
index deb0fed..954e94a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
@@ -26,11 +26,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.theme.PlatformTheme
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.people.ui.compose.PeopleScreen
-import com.android.systemui.people.ui.view.PeopleViewBinder
-import com.android.systemui.people.ui.view.PeopleViewBinder.bind
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.launch
@@ -38,10 +34,7 @@
 /** People Tile Widget configuration activity that shows the user their conversation tiles. */
 class PeopleSpaceActivity
 @Inject
-constructor(
-    private val viewModelFactory: PeopleViewModel.Factory,
-    private val featureFlags: FeatureFlags,
-) : ComponentActivity() {
+constructor(private val viewModelFactory: PeopleViewModel.Factory) : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setResult(RESULT_CANCELED)
@@ -66,17 +59,7 @@
         }
 
         // Set the content of the activity, using either the View or Compose implementation.
-        if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) {
-            Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity")
-            setContent {
-                PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) }
-            }
-        } else {
-            Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity")
-            val view = PeopleViewBinder.create(this)
-            bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) })
-            setContentView(view)
-        }
+        setContent { PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } }
     }
 
     private fun finishActivity(result: PeopleViewModel.Result) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
deleted file mode 100644
index 59c76ad..0000000
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.people;
-
-import android.app.people.PeopleSpaceTile;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.graphics.Bitmap;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.res.R;
-
-/**
- * PeopleSpaceTileView renders an individual person's tile with associated status.
- */
-public class PeopleSpaceTileView extends LinearLayout {
-
-    private View mTileView;
-    private TextView mNameView;
-    private ImageView mPersonIconView;
-
-    public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId, boolean isLast) {
-        super(context);
-        mTileView = view.findViewWithTag(shortcutId);
-        if (mTileView == null) {
-            LayoutInflater inflater = LayoutInflater.from(context);
-            mTileView = inflater.inflate(R.layout.people_space_tile_view, view, false);
-            view.addView(mTileView, LayoutParams.MATCH_PARENT,
-                    LayoutParams.MATCH_PARENT);
-            mTileView.setTag(shortcutId);
-
-            // If it's not the last conversation in this section, add a divider.
-            if (!isLast) {
-                inflater.inflate(R.layout.people_space_activity_list_divider, view, true);
-            }
-        }
-        mNameView = mTileView.findViewById(R.id.tile_view_name);
-        mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon);
-    }
-
-    /** Sets the name text on the tile. */
-    public void setName(String name) {
-        mNameView.setText(name);
-    }
-
-    /** Sets the person and package drawable on the tile. */
-    public void setPersonIcon(Bitmap bitmap) {
-        mPersonIconView.setImageBitmap(bitmap);
-    }
-
-    /** Sets the click listener of the tile. */
-    public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) {
-        mTileView.setOnClickListener(v ->
-                launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null,
-                        tile.getUserHandle()));
-    }
-
-    /** Sets the click listener of the tile directly. */
-    public void setOnClickListener(OnClickListener onClickListener) {
-        mTileView.setOnClickListener(onClickListener);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
deleted file mode 100644
index 10a2b3c..0000000
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.people.ui.view
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.Outline
-import android.graphics.drawable.GradientDrawable
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewOutlineProvider
-import android.widget.LinearLayout
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.Lifecycle.State.CREATED
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.people.PeopleSpaceTileView
-import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import com.android.systemui.res.R
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-/** A ViewBinder for [PeopleViewModel]. */
-object PeopleViewBinder {
-    private const val TAG = "PeopleViewBinder"
-
-    /**
-     * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
-     */
-    private val ViewOutlineProvider =
-        object : ViewOutlineProvider() {
-            override fun getOutline(view: View, outline: Outline) {
-                outline.setRoundRect(
-                    0,
-                    0,
-                    view.width,
-                    view.height,
-                    view.context.resources.getDimension(R.dimen.people_space_widget_radius),
-                )
-            }
-        }
-
-    /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */
-    @JvmStatic
-    fun create(context: Context): ViewGroup {
-        return LayoutInflater.from(context)
-            .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup
-    }
-
-    /** Bind [view] to [viewModel]. */
-    @JvmStatic
-    fun bind(
-        view: ViewGroup,
-        viewModel: PeopleViewModel,
-        lifecycleOwner: LifecycleOwner,
-        onResult: (PeopleViewModel.Result) -> Unit,
-    ) {
-        // Call [onResult] as soon as a result is available.
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(CREATED) {
-                viewModel.result.collect { result ->
-                    if (result != null) {
-                        viewModel.clearResult()
-                        onResult(result)
-                    }
-                }
-            }
-        }
-
-        // Start collecting the UI data once the Activity is STARTED.
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                combine(
-                        viewModel.priorityTiles,
-                        viewModel.recentTiles,
-                    ) { priority, recent ->
-                        priority to recent
-                    }
-                    .collect { (priorityTiles, recentTiles) ->
-                        if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
-                            setConversationsContent(
-                                view,
-                                priorityTiles,
-                                recentTiles,
-                                viewModel.onTileClicked,
-                            )
-                        } else {
-                            setNoConversationsContent(view, viewModel.onUserJourneyCancelled)
-                        }
-                    }
-            }
-        }
-    }
-
-    private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) {
-        // This should never happen.
-        if (view.childCount > 1) {
-            error("view has ${view.childCount} children, it should have maximum 1")
-        }
-
-        // The static content for no conversations is already shown.
-        if (view.findViewById<View>(R.id.top_level_no_conversations) != null) {
-            return
-        }
-
-        // If we were showing the content with conversations earlier, remove it.
-        if (view.childCount == 1) {
-            view.removeViewAt(0)
-        }
-
-        val context = view.context
-        val noConversationsView =
-            LayoutInflater.from(context)
-                .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
-
-        noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
-            onGotItClicked()
-        }
-
-        // The Tile preview has colorBackground as its background. Change it so it's different than
-        // the activity's background.
-        val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
-        val shape = item.background as GradientDrawable
-        val ta =
-            context.theme.obtainStyledAttributes(
-                intArrayOf(com.android.internal.R.attr.colorSurface)
-            )
-        shape.setColor(ta.getColor(0, Color.WHITE))
-        ta.recycle()
-    }
-
-    private fun setConversationsContent(
-        view: ViewGroup,
-        priorityTiles: List<PeopleTileViewModel>,
-        recentTiles: List<PeopleTileViewModel>,
-        onTileClicked: (PeopleTileViewModel) -> Unit,
-    ) {
-        // This should never happen.
-        if (view.childCount > 1) {
-            error("view has ${view.childCount} children, it should have maximum 1")
-        }
-
-        // Inflate the content with conversations, if it's not already.
-        if (view.findViewById<View>(R.id.top_level_with_conversations) == null) {
-            // If we were showing the content without conversations earlier, remove it.
-            if (view.childCount == 1) {
-                view.removeViewAt(0)
-            }
-
-            LayoutInflater.from(view.context)
-                .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view)
-        }
-
-        // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a
-        // single RecyclerView once this screen is tested by screenshot tests. Introduce a
-        // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a
-        // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class).
-        val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations)
-        setTileViews(
-            conversationsView,
-            R.id.priority,
-            R.id.priority_tiles,
-            priorityTiles,
-            onTileClicked,
-        )
-
-        setTileViews(
-            conversationsView,
-            R.id.recent,
-            R.id.recent_tiles,
-            recentTiles,
-            onTileClicked,
-        )
-    }
-
-    /** Sets a [PeopleSpaceTileView]s for each conversation. */
-    private fun setTileViews(
-        root: View,
-        tilesListId: Int,
-        tilesId: Int,
-        tiles: List<PeopleTileViewModel>,
-        onTileClicked: (PeopleTileViewModel) -> Unit,
-    ) {
-        // Remove any previously added tile.
-        // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use
-        // DiffUtil to do as less addView/removeView as possible.
-        val layout = root.requireViewById<ViewGroup>(tilesId)
-        layout.removeAllViews()
-        layout.outlineProvider = ViewOutlineProvider
-
-        val tilesListView = root.requireViewById<LinearLayout>(tilesListId)
-        if (tiles.isEmpty()) {
-            tilesListView.visibility = View.GONE
-            return
-        }
-        tilesListView.visibility = View.VISIBLE
-
-        // Add each tile.
-        tiles.forEachIndexed { i, tile ->
-            val tileView =
-                PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1)
-            bindTileView(tileView, tile, onTileClicked)
-        }
-    }
-
-    /** Sets [tileView] with the data in [conversation]. */
-    private fun bindTileView(
-        tileView: PeopleSpaceTileView,
-        tile: PeopleTileViewModel,
-        onTileClicked: (PeopleTileViewModel) -> Unit,
-    ) {
-        try {
-            tileView.setName(tile.username)
-            tileView.setPersonIcon(tile.icon)
-            tileView.setOnClickListener { onTileClicked(tile) }
-        } catch (e: Exception) {
-            Log.e(TAG, "Couldn't retrieve shortcut information", e)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 4ee2db7..cc0901f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -307,7 +307,7 @@
             } else {
                 // Set the horizontal paddings unless the view is the Compose implementation of the
                 // footer actions.
-                if (view.getTag(R.id.tag_compose_qs_footer_actions) == null) {
+                if (view.getId() != R.id.qs_footer_actions) {
                     view.setPaddingRelative(
                             mContentHorizontalPadding,
                             view.getPaddingTop(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index e424975..38d7290 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -219,6 +219,13 @@
     }
 
     @Override
+    public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {
+        if (mQsImpl != null) {
+            mQsImpl.setShouldUpdateSquishinessOnMedia(shouldUpdate);
+        }
+    }
+
+    @Override
     public void setListening(boolean listening) {
         if (mQsImpl != null) {
             mQsImpl.setListening(listening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 1f4838e..8c0d122 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -34,11 +34,11 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 
 import androidx.annotation.FloatRange;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.compose.ui.platform.ComposeView;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
@@ -48,15 +48,12 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSComponent;
-import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
@@ -117,11 +114,9 @@
     private final MediaHost mQqsMediaHost;
     private final QSDisableFlagsLogger mQsDisableFlagsLogger;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    private final FeatureFlags mFeatureFlags;
     private final QSLogger mLogger;
     private final FooterActionsController mFooterActionsController;
     private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
-    private final FooterActionsViewBinder mFooterActionsViewBinder;
     private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
     private boolean mShowCollapsedOnKeyguard;
     private boolean mLastKeyguardAndExpanded;
@@ -168,11 +163,14 @@
 
     private boolean mIsSmallScreen;
 
+    /** Should the squishiness fraction be updated on the media host. */
+    private boolean mShouldUpdateMediaSquishiness;
+
     private CommandQueue mCommandQueue;
 
     private View mRootView;
     @Nullable
-    private View mFooterActionsView;
+    private ComposeView mFooterActionsView;
 
     @Inject
     public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -184,23 +182,19 @@
             DumpManager dumpManager, QSLogger qsLogger,
             FooterActionsController footerActionsController,
             FooterActionsViewModel.Factory footerActionsViewModelFactory,
-            FooterActionsViewBinder footerActionsViewBinder,
-            LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            FeatureFlags featureFlags) {
+            LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mQsMediaHost = qsMediaHost;
         mQqsMediaHost = qqsMediaHost;
         mQsDisableFlagsLogger = qsDisableFlagsLogger;
         mLogger = qsLogger;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mFeatureFlags = featureFlags;
         mCommandQueue = commandQueue;
         mBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
         mDumpManager = dumpManager;
         mFooterActionsController = footerActionsController;
         mFooterActionsViewModelFactory = footerActionsViewModelFactory;
-        mFooterActionsViewBinder = footerActionsViewBinder;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
         if (SceneContainerFlag.isEnabled()) {
             mStatusBarState = StatusBarState.SHADE;
@@ -294,43 +288,9 @@
     }
 
     private void bindFooterActionsView(View root) {
-        LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
-
-        if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) {
-            Log.d(TAG, "Binding the View implementation of the QS footer actions");
-            mFooterActionsView = footerActionsView;
-            mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
-                    mListeningAndVisibilityLifecycleOwner);
-            return;
-        }
-
-        // Compose is available, so let's use the Compose implementation of the footer actions.
-        Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
-        View composeView = QSUtils.createFooterActionsView(root.getContext(),
+        mFooterActionsView = root.findViewById(R.id.qs_footer_actions);
+        QSUtils.setFooterActionsViewContent(mFooterActionsView,
                 mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
-        mFooterActionsView = composeView;
-
-        // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
-        // to all views except for qs_footer_actions, so we set it to the Compose view.
-        composeView.setId(R.id.qs_footer_actions);
-
-        // Set this tag so that QSContainerImpl does not add horizontal paddings to this Compose
-        // implementation of the footer actions. They will be set in Compose instead so that the
-        // background fills the full screen width.
-        composeView.setTag(R.id.tag_compose_qs_footer_actions, true);
-
-        // Set the same elevation as the View implementation, otherwise the footer actions will be
-        // drawn below the scroll view with QS grid and clicks won't get through on small devices
-        // where there isn't enough vertical space to show all the tiles and the footer actions.
-        composeView.setElevation(
-                composeView.getContext().getResources().getDimension(R.dimen.qs_panel_elevation));
-
-        // Replace the View by the Compose provided one.
-        ViewGroup parent = (ViewGroup) footerActionsView.getParent();
-        ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams();
-        int index = parent.indexOfChild(footerActionsView);
-        parent.removeViewAt(index);
-        parent.addView(composeView, index, layoutParams);
     }
 
     @Override
@@ -662,6 +622,12 @@
     }
 
     @Override
+    public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {
+        if (DEBUG) Log.d(TAG, "setShouldUpdateSquishinessOnMedia " + shouldUpdate);
+        mShouldUpdateMediaSquishiness = shouldUpdate;
+    }
+
+    @Override
     public void setQsExpansion(float expansion, float panelExpansionFraction,
             float proposedTranslation, float squishinessFraction) {
         float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
@@ -740,9 +706,11 @@
         if (mQSAnimator != null) {
             mQSAnimator.setPosition(expansion);
         }
-        if (!mInSplitShade
+        if (!mShouldUpdateMediaSquishiness
+                && (!mInSplitShade
                 || mStatusBarStateController.getState() == KEYGUARD
-                || mStatusBarStateController.getState() == SHADE_LOCKED) {
+                || mStatusBarStateController.getState() == SHADE_LOCKED)
+        ) {
             // At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
             // and media player expect no change by squishiness in lock screen shade. Don't bother
             // squishing mQsMediaHost when not in split shade to prevent problems with stale state.
@@ -1038,6 +1006,7 @@
         indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade);
         indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress);
         indentingPw.println("mOverScrolling: " + mOverScrolling);
+        indentingPw.println("mShouldUpdateMediaSquishiness: " + mShouldUpdateMediaSquishiness);
         indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing());
         View view = getView();
         if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
index 15c3f27..5482e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
@@ -1,10 +1,9 @@
 package com.android.systemui.qs
 
 import android.content.Context
-import android.view.View
+import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
-import com.android.compose.ui.platform.DensityAwareComposeView
 import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -29,13 +28,11 @@
     }
 
     @JvmStatic
-    fun createFooterActionsView(
-        context: Context,
+    fun setFooterActionsViewContent(
+        view: ComposeView,
         viewModel: FooterActionsViewModel,
         qsVisibilityLifecycleOwner: LifecycleOwner,
-    ): View {
-        return DensityAwareComposeView(context).apply {
-            setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
-        }
+    ) {
+        view.setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
deleted file mode 100644
index 0995dd4..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.footer.ui.binder
-
-import android.content.Context
-import android.graphics.PorterDuff
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.core.view.isVisible
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.people.ui.view.PeopleViewBinder.bind
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.launch
-
-/** A ViewBinder for [FooterActionsViewBinder]. */
-@SysUISingleton
-class FooterActionsViewBinder @Inject constructor() {
-    /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
-    fun create(context: Context): LinearLayout {
-        return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
-            as LinearLayout
-    }
-
-    /** Bind [view] to [viewModel]. */
-    fun bind(
-        view: LinearLayout,
-        viewModel: FooterActionsViewModel,
-        qsVisibilityLifecycleOwner: LifecycleOwner,
-    ) {
-        view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
-
-        // Add the views used by this new implementation.
-        val context = view.context
-        val inflater = LayoutInflater.from(context)
-
-        val securityHolder = TextButtonViewHolder.createAndAdd(inflater, view)
-        val foregroundServicesWithTextHolder = TextButtonViewHolder.createAndAdd(inflater, view)
-        val foregroundServicesWithNumberHolder = NumberButtonViewHolder.createAndAdd(inflater, view)
-        val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false)
-        val settingsHolder =
-            IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null)
-
-        // Bind the static power and settings buttons.
-        bindButton(settingsHolder, viewModel.settings)
-
-        if (viewModel.power != null) {
-            val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true)
-            bindButton(powerHolder, viewModel.power)
-        }
-
-        // There are 2 lifecycle scopes we are using here:
-        //   1) The scope created by [repeatWhenAttached] when [view] is attached, and destroyed
-        //      when the [view] is detached. We use this as the parent scope for all our [viewModel]
-        //      state collection, given that we don't want to do any work when [view] is detached.
-        //   2) The scope owned by [lifecycleOwner], which should be RESUMED only when Quick
-        //      Settings are visible. We use this to make sure we collect UI state only when the
-        //      View is visible.
-        //
-        // Given that we start our collection when the Quick Settings become visible, which happens
-        // every time the user swipes down the shade, we remember our previous UI state already
-        // bound to the UI to avoid binding the same values over and over for nothing.
-
-        // TODO(b/242040009): Look into using only a single scope.
-
-        var previousSecurity: FooterActionsSecurityButtonViewModel? = null
-        var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
-        var previousUserSwitcher: FooterActionsButtonViewModel? = null
-
-        // Listen for ViewModel updates when the View is attached.
-        view.repeatWhenAttached {
-            val attachedScope = this.lifecycleScope
-
-            attachedScope.launch {
-                // Listen for dialog requests as soon as we are attached, even when not visible.
-                // TODO(b/242040009): Should this move somewhere else?
-                launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) }
-
-                // Make sure we set the correct alphas even when QS are not currently shown.
-                launch { viewModel.alpha.collect { view.alpha = it } }
-                launch {
-                    viewModel.backgroundAlpha.collect {
-                        view.background?.alpha = (it * 255).roundToInt()
-                    }
-                }
-            }
-
-            // Listen for model changes only when QS are visible.
-            qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
-                // Security.
-                launch {
-                    viewModel.security.collect { security ->
-                        if (previousSecurity != security) {
-                            bindSecurity(view.context, securityHolder, security)
-                            previousSecurity = security
-                        }
-                    }
-                }
-
-                // Foreground services.
-                launch {
-                    viewModel.foregroundServices.collect { foregroundServices ->
-                        if (previousForegroundServices != foregroundServices) {
-                            bindForegroundService(
-                                foregroundServicesWithNumberHolder,
-                                foregroundServicesWithTextHolder,
-                                foregroundServices,
-                            )
-                            previousForegroundServices = foregroundServices
-                        }
-                    }
-                }
-
-                // User switcher.
-                launch {
-                    viewModel.userSwitcher.collect { userSwitcher ->
-                        if (previousUserSwitcher != userSwitcher) {
-                            bindButton(userSwitcherHolder, userSwitcher)
-                            previousUserSwitcher = userSwitcher
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private fun bindSecurity(
-        quickSettingsContext: Context,
-        securityHolder: TextButtonViewHolder,
-        security: FooterActionsSecurityButtonViewModel?,
-    ) {
-        val securityView = securityHolder.view
-        securityView.isVisible = security != null
-        if (security == null) {
-            return
-        }
-
-        // Make sure that the chevron is visible and that the button is clickable if there is a
-        // listener.
-        val chevron = securityHolder.chevron
-        val onClick = security.onClick
-        if (onClick != null) {
-            securityView.isClickable = true
-            securityView.setOnClickListener {
-                onClick(quickSettingsContext, Expandable.fromView(securityView))
-            }
-            chevron.isVisible = true
-        } else {
-            securityView.isClickable = false
-            securityView.setOnClickListener(null)
-            chevron.isVisible = false
-        }
-
-        securityHolder.text.text = security.text
-        securityHolder.newDot.isVisible = false
-        IconViewBinder.bind(security.icon, securityHolder.icon)
-    }
-
-    private fun bindForegroundService(
-        foregroundServicesWithNumberHolder: NumberButtonViewHolder,
-        foregroundServicesWithTextHolder: TextButtonViewHolder,
-        foregroundServices: FooterActionsForegroundServicesButtonViewModel?,
-    ) {
-        val foregroundServicesWithNumberView = foregroundServicesWithNumberHolder.view
-        val foregroundServicesWithTextView = foregroundServicesWithTextHolder.view
-        if (foregroundServices == null) {
-            foregroundServicesWithNumberView.isVisible = false
-            foregroundServicesWithTextView.isVisible = false
-            return
-        }
-
-        val foregroundServicesCount = foregroundServices.foregroundServicesCount
-        if (foregroundServices.displayText) {
-            // Button with text, icon and chevron.
-            foregroundServicesWithNumberView.isVisible = false
-
-            foregroundServicesWithTextView.isVisible = true
-            foregroundServicesWithTextView.setOnClickListener {
-                foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
-            }
-            foregroundServicesWithTextHolder.text.text = foregroundServices.text
-            foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
-        } else {
-            // Small button with the number only.
-            foregroundServicesWithTextView.isVisible = false
-
-            foregroundServicesWithNumberView.isVisible = true
-            foregroundServicesWithNumberView.setOnClickListener {
-                foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView))
-            }
-            foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
-            foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
-            foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
-        }
-    }
-
-    private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
-        val buttonView = button.view
-        buttonView.id = model?.id ?: View.NO_ID
-        buttonView.isVisible = model != null
-        if (model == null) {
-            return
-        }
-
-        val backgroundResource =
-            when (model.backgroundColor) {
-                R.attr.shadeInactive -> R.drawable.qs_footer_action_circle
-                R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color
-                else -> error("Unsupported icon background resource ${model.backgroundColor}")
-            }
-        buttonView.setBackgroundResource(backgroundResource)
-        buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
-
-        val icon = model.icon
-        val iconView = button.icon
-
-        IconViewBinder.bind(icon, iconView)
-        if (model.iconTint != null) {
-            iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN)
-        } else {
-            iconView.clearColorFilter()
-        }
-    }
-}
-
-private class TextButtonViewHolder(val view: View) {
-    val icon = view.requireViewById<ImageView>(R.id.icon)
-    val text = view.requireViewById<TextView>(R.id.text)
-    val newDot = view.requireViewById<ImageView>(R.id.new_dot)
-    val chevron = view.requireViewById<ImageView>(R.id.chevron_icon)
-
-    companion object {
-        fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): TextButtonViewHolder {
-            val view =
-                inflater.inflate(
-                    R.layout.footer_actions_text_button,
-                    /* root= */ root,
-                    /* attachToRoot= */ false,
-                )
-            root.addView(view)
-            return TextButtonViewHolder(view)
-        }
-    }
-}
-
-private class NumberButtonViewHolder(val view: View) {
-    val number = view.requireViewById<TextView>(R.id.number)
-    val newDot = view.requireViewById<ImageView>(R.id.new_dot)
-
-    companion object {
-        fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): NumberButtonViewHolder {
-            val view =
-                inflater.inflate(
-                    R.layout.footer_actions_number_button,
-                    /* root= */ root,
-                    /* attachToRoot= */ false,
-                )
-            root.addView(view)
-            return NumberButtonViewHolder(view)
-        }
-    }
-}
-
-private class IconButtonViewHolder(val view: View) {
-    val icon = view.requireViewById<ImageView>(R.id.icon)
-
-    companion object {
-        fun createAndAdd(
-            inflater: LayoutInflater,
-            root: ViewGroup,
-            isLast: Boolean,
-        ): IconButtonViewHolder {
-            val view =
-                inflater.inflate(
-                    R.layout.footer_actions_icon_button,
-                    /* root= */ root,
-                    /* attachToRoot= */ false,
-                )
-
-            // All buttons have a background with an inset of qs_footer_action_inset, so the last
-            // button must have a negative inset of -qs_footer_action_inset to compensate and be
-            // aligned with its parent.
-            val marginEnd =
-                if (isLast) {
-                    -view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
-                } else {
-                    0
-                }
-
-            val size =
-                view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_button_size)
-            root.addView(
-                view,
-                LinearLayout.LayoutParams(size, size).apply { this.marginEnd = marginEnd },
-            )
-            return IconButtonViewHolder(view)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
index e581bfc..095bdf2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -19,38 +19,26 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 
-/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
+/** Repository for checking if a tile should be displayed as an icon. */
 interface IconTilesRepository {
-    val iconTilesSpecs: StateFlow<Set<TileSpec>>
+    fun isIconTile(spec: TileSpec): Boolean
 }
 
 @SysUISingleton
 class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
 
-    private val _iconTilesSpecs =
-        MutableStateFlow(
-            setOf(
-                TileSpec.create("airplane"),
-                TileSpec.create("battery"),
-                TileSpec.create("cameratoggle"),
-                TileSpec.create("cast"),
-                TileSpec.create("color_correction"),
-                TileSpec.create("inversion"),
-                TileSpec.create("saver"),
-                TileSpec.create("dnd"),
-                TileSpec.create("flashlight"),
-                TileSpec.create("location"),
-                TileSpec.create("mictoggle"),
-                TileSpec.create("nfc"),
-                TileSpec.create("night"),
-                TileSpec.create("rotation")
-            )
-        )
+    override fun isIconTile(spec: TileSpec): Boolean {
+        return !LARGE_TILES.contains(spec)
+    }
 
-    /** Set of toggleable tiles that are suitable for being shown as an icon. */
-    override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow()
+    companion object {
+        private val LARGE_TILES =
+            setOf(
+                TileSpec.create("internet"),
+                TileSpec.create("bt"),
+                TileSpec.create("dnd"),
+                TileSpec.create("cast"),
+            )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index ccc1c6e..524ea8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -20,10 +20,9 @@
 import com.android.systemui.qs.panels.data.repository.IconTilesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
 
 /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
 @SysUISingleton
-class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) {
-    val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs
+class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) {
+    fun isIconTile(spec: TileSpec): Boolean = repo.isIconTile(spec)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index b437f64..e99c64c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -38,14 +38,13 @@
     override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
         val newTiles: MutableList<TileSpec> = mutableListOf()
         val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
-        val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
         val tilesQueue =
             ArrayDeque(
                 tiles.map {
                     SizedTile(
                         it,
                         width =
-                            if (iconTilesSet.contains(it)) {
+                            if (iconTilesInteractor.isIconTile(it)) {
                                 1
                             } else {
                                 2
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 4aeaa7d..2f0fe22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -52,15 +52,13 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
 
         TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
             items(
                 tiles.size,
                 span = { index ->
-                    val iconOnly = iconTilesSpecs.contains(tiles[index].spec)
-                    if (iconOnly) {
+                    if (iconTilesViewModel.isIconTile(tiles[index].spec)) {
                         GridItemSpan(1)
                     } else {
                         GridItemSpan(2)
@@ -69,7 +67,7 @@
             ) { index ->
                 Tile(
                     tile = tiles[index],
-                    iconOnly = iconTilesSpecs.contains(tiles[index].spec),
+                    iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec),
                     modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
                 )
             }
@@ -83,12 +81,11 @@
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit,
     ) {
-        val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
 
         DefaultEditTileGrid(
             tiles = tiles,
-            iconOnlySpecs = iconOnlySpecs,
+            isIconOnly = iconTilesViewModel::isIconTile,
             columns = GridCells.Fixed(columns),
             modifier = modifier,
             onAddTile = onAddTile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 708ef0d..d600767 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -38,7 +38,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
@@ -66,12 +65,11 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val iconTilesSpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val columns by viewModel.columns.collectAsStateWithLifecycle()
         val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
         val largeTileHeight = tileHeight()
         val iconTileHeight = tileHeight(showLabels)
-        val (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) }
+        val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) }
 
         TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
             // Large tiles
@@ -103,7 +101,6 @@
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit
     ) {
-        val iconOnlySpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val columns by viewModel.columns.collectAsStateWithLifecycle()
         val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
 
@@ -111,8 +108,6 @@
         val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
             onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
         }
-        val isIconOnly: (TileSpec) -> Boolean =
-            remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
         val largeTileHeight = tileHeight()
         val iconTileHeight = tileHeight(showLabels)
         val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
@@ -151,7 +146,7 @@
                 iconTileHeight = iconTileHeight,
                 tilePadding = tilePadding,
                 onRemoveTile = onRemoveTile,
-                isIconOnly = isIconOnly,
+                isIconOnly = viewModel::isIconTile,
                 columns = columns,
                 showLabels = showLabels,
             )
@@ -161,7 +156,7 @@
                 iconTileHeight = iconTileHeight,
                 tilePadding = tilePadding,
                 addTileToEnd = addTileToEnd,
-                isIconOnly = isIconOnly,
+                isIconOnly = viewModel::isIconTile,
                 showLabels = showLabels,
                 columns = columns,
             )
@@ -232,7 +227,7 @@
         val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
         val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
         val largeGridHeightCustom =
-            gridHeight(tilesCustom.size, largeTileHeight, columns / 2, tilePadding)
+            gridHeight(tilesCustom.size, iconTileHeight, columns, tilePadding)
 
         // Add up the height of all three grids and add padding in between
         val gridHeight =
@@ -257,8 +252,14 @@
                 )
                 fillUpRow(nTiles = smallTiles.size, columns = columns)
 
-                // Custom tiles, all large
-                editTiles(tilesCustom, ClickAction.ADD, addTileToEnd, isIconOnly)
+                // Custom tiles, all icons
+                editTiles(
+                    tilesCustom,
+                    ClickAction.ADD,
+                    addTileToEnd,
+                    isIconOnly,
+                    showLabels = showLabels
+                )
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index 70d629f..7f4e0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -60,14 +60,13 @@
         // Icon [3 | 4]
         // Large [6 | 8]
         val columns = 12
-        val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val stretchedTiles =
             remember(tiles) {
                 val sizedTiles =
                     tiles.map {
                         SizedTile(
                             it,
-                            if (iconTilesSpecs.contains(it.spec)) {
+                            if (iconTilesViewModel.isIconTile(it.spec)) {
                                 3
                             } else {
                                 6
@@ -81,7 +80,7 @@
             items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
                 Tile(
                     tile = stretchedTiles[index].tile,
-                    iconOnly = iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+                    iconOnly = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec),
                     modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
                 )
             }
@@ -95,12 +94,11 @@
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit
     ) {
-        val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
 
         DefaultEditTileGrid(
             tiles = tiles,
-            iconOnlySpecs = iconOnlySpecs,
+            isIconOnly = iconTilesViewModel::isIconTile,
             columns = GridCells.Fixed(columns),
             modifier = modifier,
             onAddTile = onAddTile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index a6838c0..f776bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -165,7 +165,7 @@
 @Composable
 fun DefaultEditTileGrid(
     tiles: List<EditTileViewModel>,
-    iconOnlySpecs: Set<TileSpec>,
+    isIconOnly: (TileSpec) -> Boolean,
     columns: GridCells,
     modifier: Modifier,
     onAddTile: (TileSpec, Int) -> Unit,
@@ -176,8 +176,6 @@
     val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
         onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
     }
-    val isIconOnly: (TileSpec) -> Boolean =
-        remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
 
     TileLazyGrid(modifier = modifier, columns = columns) {
         // These Text are just placeholders to see the different sections. Not final UI.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 9ad00c8..117c85c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -20,14 +20,13 @@
 import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
 
 interface IconTilesViewModel {
-    val iconTilesSpecs: StateFlow<Set<TileSpec>>
+    fun isIconTile(spec: TileSpec): Boolean
 }
 
 @SysUISingleton
-class IconTilesViewModelImpl @Inject constructor(interactor: IconTilesInteractor) :
+class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
     IconTilesViewModel {
-    override val iconTilesSpecs = interactor.iconTilesSpecs
+    override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 214e9f0..24b80b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -158,6 +158,9 @@
     override suspend fun prependDefault(
         userId: Int,
     ) {
+        if (retailModeRepository.inRetailMode) {
+            return
+        }
         userTileRepositories.get(userId)?.prependDefault()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 8ad5cb2..aca8733 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -60,15 +60,21 @@
             _tiles =
                 changeEvents
                     .scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
-                        change.apply(current).also {
-                            if (current != it) {
-                                if (change is RestoreTiles) {
-                                    logger.logTilesRestoredAndReconciled(current, it, userId)
-                                } else {
-                                    logger.logProcessTileChange(change, it, userId)
+                        change
+                            .apply(current)
+                            .also {
+                                if (current != it) {
+                                    if (change is RestoreTiles) {
+                                        logger.logTilesRestoredAndReconciled(current, it, userId)
+                                    } else {
+                                        logger.logProcessTileChange(change, it, userId)
+                                    }
                                 }
                             }
-                        }
+                            // Distinct preserves the order of the elements removing later
+                            // duplicates,
+                            // all tiles should be different
+                            .distinct()
                     }
                     .flowOn(backgroundDispatcher)
                     .stateIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index b7fcef4..97b5e87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.qs.toProto
+import com.android.systemui.retail.data.repository.RetailModeRepository
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
@@ -137,6 +138,7 @@
     private val installedTilesComponentRepository: InstalledTilesComponentRepository,
     private val userRepository: UserRepository,
     private val minimumTilesRepository: MinimumTilesRepository,
+    private val retailModeRepository: RetailModeRepository,
     private val customTileStatePersister: CustomTileStatePersister,
     private val newQSTileFactory: Lazy<NewQSTileFactory>,
     private val tileFactory: QSFactory,
@@ -178,6 +180,14 @@
             installedTilesComponentRepository.getInstalledTilesComponents(it)
         }
 
+    private val minTiles: Int
+        get() =
+            if (retailModeRepository.inRetailMode) {
+                1
+            } else {
+                minimumTilesRepository.minNumberOfTiles
+            }
+
     init {
         if (featureFlags.pipelineEnabled) {
             startTileCollection()
@@ -273,7 +283,7 @@
                             newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
                             newUser
                         )
-                        if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) {
+                        if (newResolvedTiles.size < minTiles) {
                             // We ended up with not enough tiles (some may be not installed).
                             // Prepend the default set of tiles
                             launch { tileSpecRepository.prependDefault(currentUser.value) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index c6dfdd5..1143c30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -332,6 +332,21 @@
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
         super.onLayout(changed, l, t, r, b)
         updateHeight()
+        maybeUpdateLongPressEffectDimensions()
+    }
+
+    private fun maybeUpdateLongPressEffectDimensions() {
+        if (!isLongClickable || longPressEffect == null) return
+
+        val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
+            heightOverride
+        } else {
+            measuredHeight
+        }
+        initialLongPressProperties?.height = actualHeight.toFloat()
+        initialLongPressProperties?.width = measuredWidth.toFloat()
+        finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * actualHeight
+        finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * measuredWidth
     }
 
     override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 1d8b7e5b..bf0843b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -20,10 +20,12 @@
 import android.graphics.Rect
 import android.os.PowerManager
 import android.os.SystemClock
+import android.util.ArraySet
 import android.view.GestureDetector
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
@@ -35,6 +37,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags.glanceableHubFullscreenSwipe
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
 import com.android.systemui.communal.dagger.Communal
@@ -52,10 +55,12 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.collectFlow
+import java.util.function.Consumer
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.launch
@@ -77,11 +82,38 @@
     private val communalColors: CommunalColors,
     private val ambientTouchComponentFactory: AmbientTouchComponent.Factory,
     private val communalContent: CommunalContent,
-    @Communal private val dataSourceDelegator: SceneDataSourceDelegator
+    @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
+    private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
 ) : LifecycleOwner {
+
+    private class CommunalWrapper(context: Context) : FrameLayout(context) {
+        private val consumers: MutableSet<Consumer<Boolean>> = ArraySet()
+
+        override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
+            consumers.forEach { it.accept(disallowIntercept) }
+            super.requestDisallowInterceptTouchEvent(disallowIntercept)
+        }
+
+        fun dispatchTouchEvent(
+            ev: MotionEvent?,
+            disallowInterceptConsumer: Consumer<Boolean>?
+        ): Boolean {
+            disallowInterceptConsumer?.apply { consumers.add(this) }
+
+            try {
+                return super.dispatchTouchEvent(ev)
+            } finally {
+                consumers.clear()
+            }
+        }
+    }
+
     /** The container view for the hub. This will not be initialized until [initView] is called. */
     private var communalContainerView: View? = null
 
+    /** Wrapper around the communal container to intercept touch events */
+    private var communalContainerWrapper: CommunalWrapper? = null
+
     /**
      * This lifecycle is used to control when the [touchMonitor] listens to touches. The lifecycle
      * should only be [Lifecycle.State.RESUMED] when the hub is showing and not covered by anything,
@@ -271,9 +303,13 @@
         )
         collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
 
-        communalContainerView = containerView
-
-        return containerView
+        if (glanceableHubFullscreenSwipe()) {
+            communalContainerWrapper = CommunalWrapper(containerView.context)
+            communalContainerWrapper?.addView(communalContainerView)
+            return communalContainerWrapper!!
+        } else {
+            return containerView
+        }
     }
 
     /**
@@ -306,6 +342,11 @@
             lifecycleRegistry.currentState = Lifecycle.State.CREATED
             communalContainerView = null
         }
+
+        communalContainerWrapper?.let {
+            (it.parent as ViewGroup).removeView(it)
+            communalContainerWrapper = null
+        }
     }
 
     /**
@@ -319,6 +360,18 @@
      */
     fun onTouchEvent(ev: MotionEvent): Boolean {
         SceneContainerFlag.assertInLegacyMode()
+
+        // In the case that we are handling full swipes on the lockscreen, are on the lockscreen,
+        // and the touch is within the horizontal notification band on the screen, do not process
+        // the touch.
+        if (
+            glanceableHubFullscreenSwipe() &&
+                !hubShowing &&
+                !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y)
+        ) {
+            return false
+        }
+
         return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
     }
 
@@ -330,12 +383,16 @@
         val hubOccluded = anyBouncerShowing || shadeShowing
 
         if (isDown && !hubOccluded) {
-            val x = ev.rawX
-            val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
-            if (inOpeningSwipeRegion || hubShowing) {
-                // Steal touch events when the hub is open, or if the touch started in the opening
-                // gesture region.
+            if (glanceableHubFullscreenSwipe()) {
                 isTrackingHubTouch = true
+            } else {
+                val x = ev.rawX
+                val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
+                if (inOpeningSwipeRegion || hubShowing) {
+                    // Steal touch events when the hub is open, or if the touch started in the
+                    // opening gesture region.
+                    isTrackingHubTouch = true
+                }
             }
         }
 
@@ -343,10 +400,7 @@
             if (isUp || isCancel) {
                 isTrackingHubTouch = false
             }
-            dispatchTouchEvent(view, ev)
-            // Return true regardless of dispatch result as some touches at the start of a gesture
-            // may return false from dispatchTouchEvent.
-            return true
+            return dispatchTouchEvent(view, ev)
         }
 
         return false
@@ -356,13 +410,30 @@
      * Dispatches the touch event to the communal container and sends a user activity event to reset
      * the screen timeout.
      */
-    private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
-        view.dispatchTouchEvent(ev)
-        powerManager.userActivity(
-            SystemClock.uptimeMillis(),
-            PowerManager.USER_ACTIVITY_EVENT_TOUCH,
-            0
-        )
+    private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean {
+        try {
+            var handled = false
+            if (glanceableHubFullscreenSwipe()) {
+                communalContainerWrapper?.dispatchTouchEvent(ev) {
+                    if (it) {
+                        handled = true
+                    }
+                }
+                return handled || hubShowing
+            } else {
+                view.dispatchTouchEvent(ev)
+                // Return true regardless of dispatch result as some touches at the start of a
+                // gesture
+                // may return false from dispatchTouchEvent.
+                return true
+            }
+        } finally {
+            powerManager.userActivity(
+                SystemClock.uptimeMillis(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+                0
+            )
+        }
     }
 
     override val lifecycle: Lifecycle
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 6df8ac4..4f6a64f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -65,6 +65,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.classifier.Classifier;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -157,6 +158,7 @@
     private final ShadeRepository mShadeRepository;
     private final ShadeInteractor mShadeInteractor;
     private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
+    private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModelLazy;
     private final JavaAdapter mJavaAdapter;
     private final FalsingManager mFalsingManager;
     private final AccessibilityManager mAccessibilityManager;
@@ -334,6 +336,7 @@
             JavaAdapter javaAdapter,
             CastController castController,
             SplitShadeStateController splitShadeStateController,
+            Lazy<CommunalTransitionViewModel> communalTransitionViewModelLazy,
             Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy
     ) {
         SceneContainerFlag.assertInLegacyMode();
@@ -379,6 +382,7 @@
         mShadeRepository = shadeRepository;
         mShadeInteractor = shadeInteractor;
         mActiveNotificationsInteractor = activeNotificationsInteractor;
+        mCommunalTransitionViewModelLazy = communalTransitionViewModelLazy;
         mJavaAdapter = javaAdapter;
 
         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
@@ -458,6 +462,9 @@
         initNotificationStackScrollLayoutController();
         mJavaAdapter.alwaysCollectFlow(
                 mShadeInteractor.isExpandToQsEnabled(), this::setExpansionEnabledPolicy);
+        mJavaAdapter.alwaysCollectFlow(
+                mCommunalTransitionViewModelLazy.get().isUmoOnCommunal(),
+                this::setShouldUpdateSquishinessOnMedia);
     }
 
     private void initNotificationStackScrollLayoutController() {
@@ -892,6 +899,12 @@
         }
     }
 
+    private void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {
+        if (mQs != null) {
+            mQs.setShouldUpdateSquishinessOnMedia(shouldUpdate);
+        }
+    }
+
     void setOverScrollAmount(int overExpansion) {
         if (mQs != null) {
             mQs.setOverScrollAmount(overExpansion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index d00916a..c742f641 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -610,7 +610,7 @@
             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
                     calendarIcon,
-                    KeyEvent.KEYCODE_L,
+                    KeyEvent.KEYCODE_K,
                     KeyEvent.META_META_ON));
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 3cf61e2..8d3f728 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -362,20 +362,34 @@
         }
 
         protected boolean hasSameIcon(Object parentData, Object childData) {
-            Icon parentIcon = ((Notification) parentData).getSmallIcon();
-            Icon childIcon = ((Notification) childData).getSmallIcon();
+            Icon parentIcon = getIcon((Notification) parentData);
+            Icon childIcon = getIcon((Notification) childData);
             return parentIcon.sameAs(childIcon);
         }
 
+        private static Icon getIcon(Notification notification) {
+            if (notification.shouldUseAppIcon()) {
+                return notification.getAppIcon();
+            }
+            return notification.getSmallIcon();
+        }
+
         /**
          * @return whether two ImageViews have the same colorFilterSet or none at all
          */
         protected boolean hasSameColor(Object parentData, Object childData) {
-            int parentColor = ((Notification) parentData).color;
-            int childColor = ((Notification) childData).color;
+            int parentColor = getColor((Notification) parentData);
+            int childColor = getColor((Notification) childData);
             return parentColor == childColor;
         }
 
+        private static int getColor(Notification notification) {
+            if (notification.shouldUseAppIcon()) {
+                return 0;  // the color filter isn't applied if using the app icon
+            }
+            return notification.color;
+        }
+
         @Override
         public boolean isEmpty(View view) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index 319b499..16d0cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -18,25 +18,27 @@
 
 import android.app.Notification
 import android.content.Context
+import android.graphics.drawable.Drawable
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.contentDescForNotification
 import javax.inject.Inject
 
-/**
- * Testable wrapper around Context.
- */
-class IconBuilder @Inject constructor(
-    private val context: Context
-) {
+/** Testable wrapper around Context. */
+class IconBuilder @Inject constructor(private val context: Context) {
     fun createIconView(entry: NotificationEntry): StatusBarIconView {
         return StatusBarIconView(
-                context,
-                "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
-                entry.sbn)
+            context,
+            "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
+            entry.sbn
+        )
     }
 
     fun getIconContentDescription(n: Notification): CharSequence {
         return contentDescForNotification(context, n)
     }
+
+    fun getAppIcon(n: Notification): Drawable {
+        return n.loadHeaderAppIcon(context)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 271b0a8..3df9374 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -20,6 +20,8 @@
 import android.app.Notification.MessagingStyle
 import android.app.Person
 import android.content.pm.LauncherApps
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.os.Build
 import android.os.Bundle
@@ -165,7 +167,7 @@
                 Log.wtf(
                     TAG,
                     "Updating using the cache is not supported when the " +
-                        "notifications_background_conversation_icons flag is off"
+                        "notifications_background_icons flag is off"
                 )
             }
             if (!usingCache || !Flags.notificationsBackgroundIcons()) {
@@ -216,39 +218,85 @@
 
     @Throws(InflationException::class)
     private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
-        val n = entry.sbn.notification
         val showPeopleAvatar = !redact && isImportantConversation(entry)
 
+        // If the descriptor is already cached, return it
+        getCachedIconDescriptor(entry, showPeopleAvatar)?.also {
+            return it
+        }
+
+        val n = entry.sbn.notification
+        var usingMonochromeAppIcon = false
+        val icon: Icon?
+        if (showPeopleAvatar) {
+            icon = createPeopleAvatar(entry)
+        } else if (android.app.Flags.notificationsUseMonochromeAppIcon()) {
+            if (n.shouldUseAppIcon()) {
+                icon =
+                    getMonochromeAppIcon(entry)?.also { usingMonochromeAppIcon = true }
+                        ?: n.smallIcon
+            } else {
+                icon = n.smallIcon
+            }
+        } else {
+            icon = n.smallIcon
+        }
+
+        if (icon == null) {
+            throw InflationException("No icon in notification from ${entry.sbn.packageName}")
+        }
+
+        val sbi = icon.toStatusBarIcon(entry)
+        cacheIconDescriptor(entry, sbi, showPeopleAvatar, usingMonochromeAppIcon)
+        return sbi
+    }
+
+    private fun getCachedIconDescriptor(
+        entry: NotificationEntry,
+        showPeopleAvatar: Boolean
+    ): StatusBarIcon? {
         val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
+        val appIconDescriptor = entry.icons.appIconDescriptor
         val smallIconDescriptor = entry.icons.smallIconDescriptor
 
         // If cached, return corresponding cached values
-        if (showPeopleAvatar && peopleAvatarDescriptor != null) {
-            return peopleAvatarDescriptor
-        } else if (!showPeopleAvatar && smallIconDescriptor != null) {
-            return smallIconDescriptor
+        return when {
+            showPeopleAvatar && peopleAvatarDescriptor != null -> peopleAvatarDescriptor
+            android.app.Flags.notificationsUseMonochromeAppIcon() && appIconDescriptor != null ->
+                appIconDescriptor
+            smallIconDescriptor != null -> smallIconDescriptor
+            else -> null
         }
+    }
 
-        val icon =
-            (if (showPeopleAvatar) {
-                createPeopleAvatar(entry)
-            } else {
-                n.smallIcon
-            })
-                ?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
-
-        val sbi = icon.toStatusBarIcon(entry)
-
-        // Cache if important conversation or app icon.
-        if (isImportantConversation(entry) || android.app.Flags.notificationsUseAppIcon()) {
+    private fun cacheIconDescriptor(
+        entry: NotificationEntry,
+        descriptor: StatusBarIcon,
+        showPeopleAvatar: Boolean,
+        usingMonochromeAppIcon: Boolean
+    ) {
+        if (android.app.Flags.notificationsUseAppIcon() ||
+            android.app.Flags.notificationsUseMonochromeAppIcon()
+        ) {
+            // If either of the new icon flags is enabled, we cache the icon all the time.
             if (showPeopleAvatar) {
-                entry.icons.peopleAvatarDescriptor = sbi
+                entry.icons.peopleAvatarDescriptor = descriptor
+            } else if (usingMonochromeAppIcon) {
+                // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor.
+                entry.icons.appIconDescriptor = descriptor
             } else {
-                entry.icons.smallIconDescriptor = sbi
+                // When notificationsUseAppIcon is enabled, the app icon overrides the small icon.
+                // But either way, it's a good idea to cache the descriptor.
+                entry.icons.smallIconDescriptor = descriptor
+            }
+        } else if (isImportantConversation(entry)) {
+            // Old approach: cache only if important conversation.
+            if (showPeopleAvatar) {
+                entry.icons.peopleAvatarDescriptor = descriptor
+            } else {
+                entry.icons.smallIconDescriptor = descriptor
             }
         }
-
-        return sbi
     }
 
     @Throws(InflationException::class)
@@ -276,6 +324,29 @@
         )
     }
 
+    // TODO(b/335211019): Should we merge this with the method in GroupHelper?
+    private fun getMonochromeAppIcon(entry: NotificationEntry): Icon? {
+        // TODO(b/335211019): This should be done in the background.
+        var monochromeIcon: Icon? = null
+        try {
+            val appIcon: Drawable = iconBuilder.getAppIcon(entry.sbn.notification)
+            if (appIcon is AdaptiveIconDrawable) {
+                if (appIcon.monochrome != null) {
+                    monochromeIcon =
+                        Icon.createWithResourceAdaptiveDrawable(
+                            /* resPackage = */ entry.sbn.packageName,
+                            /* resId = */ appIcon.sourceDrawableResId,
+                            /* useMonochrome = */ true,
+                            /* inset = */ -3.0f * AdaptiveIconDrawable.getExtraInsetFraction()
+                        )
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to getAppIcon() in getMonochromeAppIcon()", e)
+        }
+        return monochromeIcon
+    }
+
     private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
         withContext(bgCoroutineContext) {
             var icon: Icon? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 442c097..d029ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -33,6 +33,7 @@
     @Nullable private final StatusBarIconView mAodIcon;
 
     @Nullable private StatusBarIcon mSmallIconDescriptor;
+    @Nullable private StatusBarIcon mAppIconDescriptor;
     @Nullable private StatusBarIcon mPeopleAvatarDescriptor;
 
     private boolean mIsImportantConversation;
@@ -111,6 +112,15 @@
         mPeopleAvatarDescriptor = peopleAvatarDescriptor;
     }
 
+    @Nullable
+    StatusBarIcon getAppIconDescriptor() {
+        return mAppIconDescriptor;
+    }
+
+    void setAppIconDescriptor(@Nullable StatusBarIcon appIconDescriptor) {
+        mAppIconDescriptor = appIconDescriptor;
+    }
+
     boolean isImportantConversation() {
         return mIsImportantConversation;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
index c74c396..c29d700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
@@ -21,9 +21,9 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.util.Log
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 
 // Class to track avalanche trigger event time.
@@ -33,6 +33,7 @@
 constructor(
         private val broadcastDispatcher: BroadcastDispatcher,
         private val logger: VisualInterruptionDecisionLogger,
+        private val uiEventLogger: UiEventLogger,
 ) {
     val TAG = "AvalancheProvider"
     val timeoutMs = 120000
@@ -56,6 +57,7 @@
                     return
                 }
                 Log.d(TAG, "broadcastReceiver received intent.action=" + intent.action)
+                uiEventLogger.log(AvalancheSuppressor.AvalancheEvent.START);
                 startTime = System.currentTimeMillis()
             }
         }
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 938a71f..42a5bdf 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
@@ -33,6 +33,8 @@
 import android.provider.Settings
 import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
 import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEvent;
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
@@ -241,12 +243,12 @@
     override fun shouldSuppress(entry: NotificationEntry) =
         keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
 }
-
 class AvalancheSuppressor(
     private val avalancheProvider: AvalancheProvider,
     private val systemClock: SystemClock,
     private val systemSettings: SystemSettings,
     private val packageManager: PackageManager,
+    private val uiEventLogger: UiEventLogger,
 ) :
     VisualInterruptionFilter(
         types = setOf(PEEK, PULSE),
@@ -266,6 +268,44 @@
         SUPPRESS
     }
 
+     enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+         @UiEvent(doc = "An avalanche event occurred but this notification was suppressed by a " +
+                 "non-avalanche suppressor.")
+         START(1802),
+
+         @UiEvent(doc = "HUN was suppressed in avalanche.")
+         SUPPRESS(1803),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is high priority.")
+         ALLOW_CONVERSATION_AFTER_AVALANCHE(1804),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.")
+         ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is a call.")
+         ALLOW_CALLSTYLE(1806),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+         ALLOW_CATEGORY_REMINDER(1807),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+         ALLOW_CATEGORY_EVENT(1808),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it has a full screen intent and " +
+                 "the full screen intent permission is granted.")
+         ALLOW_FSI_WITH_PERMISSION_ON(1809),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
+         ALLOW_COLORIZED(1810),
+
+         @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
+         ALLOW_EMERGENCY(1811);
+
+        override fun getId(): Int {
+            return id
+        }
+    }
+
     override fun shouldSuppress(entry: NotificationEntry): Boolean {
         if (!isCooldownEnabled()) {
             return false
@@ -287,41 +327,46 @@
             entry.ranking.isConversation &&
                 entry.sbn.notification.getWhen() > avalancheProvider.startTime
         ) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_CONVERSATION_AFTER_AVALANCHE)
             return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
         }
 
         if (entry.channel?.isImportantConversation == true) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME)
             return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME
         }
 
         if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_CALLSTYLE)
             return State.ALLOW_CALLSTYLE
         }
 
         if (entry.sbn.notification.category == CATEGORY_REMINDER) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_REMINDER)
             return State.ALLOW_CATEGORY_REMINDER
         }
 
         if (entry.sbn.notification.category == CATEGORY_EVENT) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_EVENT)
             return State.ALLOW_CATEGORY_EVENT
         }
 
         if (entry.sbn.notification.fullScreenIntent != null) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_FSI_WITH_PERMISSION_ON)
             return State.ALLOW_FSI_WITH_PERMISSION_ON
         }
-
         if (entry.sbn.notification.isColorized) {
-            return State.ALLOW_COLORIZED
-        }
-        if (entry.sbn.notification.isColorized) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_COLORIZED)
             return State.ALLOW_COLORIZED
         }
         if (
             packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
                 PERMISSION_GRANTED
         ) {
+            uiEventLogger.log(AvalancheEvent.ALLOW_EMERGENCY)
             return State.ALLOW_EMERGENCY
         }
+        uiEventLogger.log(AvalancheEvent.SUPPRESS)
         return State.SUPPRESS
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 7e16cd5..84f8662 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -178,7 +178,8 @@
 
         if (NotificationAvalancheSuppression.isEnabled) {
             addFilter(
-                AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+                AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                        uiEventLogger)
             )
             avalancheProvider.register()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index bd7f766..d1fabb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -191,8 +191,12 @@
         updateTransformedTypes();
         addRemainingTransformTypes();
         updateCropToPaddingForImageViews();
-        Notification notification = row.getEntry().getSbn().getNotification();
-        mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
+        Notification n = row.getEntry().getSbn().getNotification();
+        if (n.shouldUseAppIcon()) {
+            mIcon.setTag(ImageTransformState.ICON_TAG, n.getAppIcon());
+        } else {
+            mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon());
+        }
 
         // We need to reset all views that are no longer transforming in case a view was previously
         // transformed, but now we decided to transform its container instead.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index d6c73a9..2dccea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -16,24 +16,22 @@
 
 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 heads-up cycling flag state. */
 @Suppress("NOTHING_TO_INLINE")
 object NotificationHeadsUpCycling {
-    /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
+    /** The aconfig flag name */
+    const val FLAG_NAME = NotificationThrottleHun.FLAG_NAME
 
     /** A token used for dependency declaration */
     val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
+        get() = NotificationThrottleHun.token
 
     /** Is the heads-up cycling animation enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationThrottleHun()
+        get() = NotificationThrottleHun.isEnabled
 
     /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
     @JvmStatic
@@ -46,13 +44,12 @@
      * build to ensure that the refactor author catches issues in testing.
      */
     @JvmStatic
-    inline fun isUnexpectedlyInLegacyMode() =
-        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+    inline fun isUnexpectedlyInLegacyMode() = NotificationThrottleHun.isUnexpectedlyInLegacyMode()
 
     /**
      * 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)
+    inline fun assertInLegacyMode() = NotificationThrottleHun.assertInLegacyMode()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
index dd81d42..71f0de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
@@ -24,7 +24,7 @@
 @Suppress("NOTHING_TO_INLINE")
 object NotificationThrottleHun {
     /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -33,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationThrottleHun()
+        get() = Flags.notificationAvalancheThrottleHun()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index db544ce..f6722a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -38,9 +38,6 @@
      */
     val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
 
-    /** the y position of the top of the HUN area */
-    val headsUpTop = MutableStateFlow(0f)
-
     /** height made available to the notifications in the size-constrained mode of lock screen. */
     val constrainedAvailableSpace = MutableStateFlow(0)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index afcf3ae..8557afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -79,9 +79,6 @@
      */
     val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow()
 
-    /** The y-coordinate in px of bottom of the contents of the HUN. */
-    val headsUpTop: StateFlow<Float> = placeholderRepository.headsUpTop.asStateFlow()
-
     /**
      * The amount in px that the notification stack should scroll due to internal expansion. This
      * should only happen when a notification expansion hits the bottom of the screen, so it is
@@ -125,8 +122,4 @@
     fun setConstrainedAvailableSpace(height: Int) {
         placeholderRepository.constrainedAvailableSpace.value = height
     }
-
-    fun setHeadsUpTop(headsUpTop: Float) {
-        placeholderRepository.headsUpTop.value = headsUpTop
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index a99fbfc..e90a64a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -139,8 +139,6 @@
      */
     val scrolledToTop: Flow<Boolean> =
         stackAppearanceInteractor.scrolledToTop.dumpValue("scrolledToTop")
-    /** The y-coordinate in px of bottom of the contents of the HUN. */
-    val headsUpTop: Flow<Float> = stackAppearanceInteractor.headsUpTop.dumpValue("headsUpTop")
 
     /** Receives the amount (px) that the stack should scroll due to internal expansion. */
     val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll
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 ea33be0..634bd7e 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
@@ -60,10 +60,6 @@
         interactor.setConstrainedAvailableSpace(height)
     }
 
-    fun onHeadsUpTopChanged(headsUpTop: Float) {
-        interactor.setHeadsUpTop(headsUpTop)
-    }
-
     /** Sets the content alpha for the current state of the brightness mirror */
     fun setAlphaForBrightnessMirror(alpha: Float) {
         interactor.setAlphaForBrightnessMirror(alpha)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index eb09e6e..a972985 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -17,6 +17,8 @@
 
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
@@ -35,6 +37,7 @@
 @Inject
 constructor(
     dumpManager: DumpManager,
+    private val uiEventLogger: UiEventLogger
 ) : Dumpable {
 
     private val tag = "AvalancheController"
@@ -65,6 +68,21 @@
     // For debugging only
     @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
 
+    enum class ThrottleEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "HUN was shown.")
+        SHOWN(1812),
+
+        @UiEvent(doc = "HUN was dropped to show higher priority HUNs.")
+        DROPPED(1813),
+
+        @UiEvent(doc = "HUN was removed while waiting to show.")
+        REMOVED(1814);
+
+        override fun getId(): Int {
+            return id
+        }
+    }
+
     init {
         dumpManager.registerNormalDumpable(tag, /* module */ this)
     }
@@ -145,6 +163,7 @@
             log { "$fn => remove from next" }
             if (entry in nextMap) nextMap.remove(entry)
             if (entry in nextList) nextList.remove(entry)
+            uiEventLogger.log(ThrottleEvent.REMOVED)
         } else if (entry in debugDropSet) {
             log { "$fn => remove from dropset" }
             debugDropSet.remove(entry)
@@ -268,6 +287,7 @@
     private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
         log { "SHOW: " + getKey(entry) }
 
+        uiEventLogger.log(ThrottleEvent.SHOWN)
         headsUpEntryShowing = entry
 
         runnableList.forEach {
@@ -295,6 +315,12 @@
 
         // Remove runnable labels for dropped huns
         val listToDrop = nextList.subList(1, nextList.size)
+
+        // Log dropped HUNs
+        for (e in listToDrop) {
+            uiEventLogger.log(ThrottleEvent.DROPPED)
+        }
+
         if (debug) {
             // Clear runnable labels
             for (e in listToDrop) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
deleted file mode 100644
index 2cad844..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.concurrency;
-
-import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-
-import com.android.systemui.Flags;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.BroadcastRunning;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.NotifInflation;
-
-import dagger.Module;
-import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
-/**
- * Dagger Module for classes found within the concurrent package.
- */
-@Module
-public abstract class SysUIConcurrencyModule {
-
-    // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this
-    // thread
-    private static final Long BG_SLOW_DISPATCH_THRESHOLD = 1000L;
-    private static final Long BG_SLOW_DELIVERY_THRESHOLD = 1000L;
-    private static final Long LONG_SLOW_DISPATCH_THRESHOLD = 2500L;
-    private static final Long LONG_SLOW_DELIVERY_THRESHOLD = 2500L;
-    private static final Long BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L;
-    private static final Long BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L;
-    private static final Long NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L;
-    private static final Long NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L;
-
-    /** Background Looper */
-    @Provides
-    @SysUISingleton
-    @Background
-    public static Looper provideBgLooper() {
-        HandlerThread thread = new HandlerThread("SysUiBg",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        thread.getLooper().setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD,
-                BG_SLOW_DELIVERY_THRESHOLD);
-        return thread.getLooper();
-    }
-
-    /** BroadcastRunning Looper (for sending and receiving broadcasts) */
-    @Provides
-    @SysUISingleton
-    @BroadcastRunning
-    public static Looper provideBroadcastRunningLooper() {
-        HandlerThread thread = new HandlerThread("BroadcastRunning",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        thread.getLooper().setSlowLogThresholdMs(BROADCAST_SLOW_DISPATCH_THRESHOLD,
-                BROADCAST_SLOW_DELIVERY_THRESHOLD);
-        return thread.getLooper();
-    }
-
-    /** Long running tasks Looper */
-    @Provides
-    @SysUISingleton
-    @LongRunning
-    public static Looper provideLongRunningLooper() {
-        HandlerThread thread = new HandlerThread("SysUiLng",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        thread.getLooper().setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD,
-                LONG_SLOW_DELIVERY_THRESHOLD);
-        return thread.getLooper();
-    }
-
-    /** Notification inflation Looper */
-    @Provides
-    @SysUISingleton
-    @NotifInflation
-    public static Looper provideNotifInflationLooper(@Background Looper bgLooper) {
-        if (!Flags.dedicatedNotifInflationThread()) {
-            return bgLooper;
-        }
-
-        final HandlerThread thread = new HandlerThread("NotifInflation",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        final Looper looper = thread.getLooper();
-        looper.setSlowLogThresholdMs(NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD,
-                NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD);
-        return looper;
-    }
-
-    /**
-     * Background Handler.
-     *
-     * Prefer the Background Executor when possible.
-     */
-    @Provides
-    @Background
-    public static Handler provideBgHandler(@Background Looper bgLooper) {
-        return new Handler(bgLooper);
-    }
-
-    /**
-     * Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
-     */
-    @Provides
-    @SysUISingleton
-    @BroadcastRunning
-    public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) {
-        return new ExecutorImpl(looper);
-    }
-
-    /**
-     * Provide a Long running Executor.
-     */
-    @Provides
-    @SysUISingleton
-    @LongRunning
-    public static Executor provideLongRunningExecutor(@LongRunning Looper looper) {
-        return new ExecutorImpl(looper);
-    }
-
-    /**
-     * Provide a Long running Executor.
-     */
-    @Provides
-    @SysUISingleton
-    @LongRunning
-    public static DelayableExecutor provideLongRunningDelayableExecutor(
-            @LongRunning Looper looper) {
-        return new ExecutorImpl(looper);
-    }
-
-    /**
-     * Provide a Background-Thread Executor.
-     */
-    @Provides
-    @SysUISingleton
-    @Background
-    public static Executor provideBackgroundExecutor(@Background Looper looper) {
-        return new ExecutorImpl(looper);
-    }
-
-    /**
-     * Provide a Background-Thread Executor.
-     */
-    @Provides
-    @SysUISingleton
-    @Background
-    public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(looper);
-    }
-
-    /**
-     * Provide a Background-Thread Executor.
-     */
-    @Provides
-    @SysUISingleton
-    @Background
-    public static RepeatableExecutor provideBackgroundRepeatableExecutor(
-            @Background DelayableExecutor exec) {
-        return new RepeatableExecutorImpl(exec);
-    }
-
-    /**
-     * Provide a Main-Thread Executor.
-     */
-    @Provides
-    @SysUISingleton
-    @Main
-    public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
-        return new RepeatableExecutorImpl(exec);
-    }
-
-    /** */
-    @Provides
-    @Main
-    public static MessageRouter providesMainMessageRouter(
-            @Main DelayableExecutor executor) {
-        return new MessageRouterImpl(executor);
-    }
-
-    /** */
-    @Provides
-    @Background
-    public static MessageRouter providesBackgroundMessageRouter(
-            @Background DelayableExecutor executor) {
-        return new MessageRouterImpl(executor);
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    @Named(TIME_TICK_HANDLER_NAME)
-    public static Handler provideTimeTickHandler() {
-        HandlerThread thread = new HandlerThread("TimeTick");
-        thread.start();
-        return new Handler(thread.getLooper());
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    @NotifInflation
-    public static Executor provideNotifInflationExecutor(@NotifInflation Looper looper) {
-        return new ExecutorImpl(looper);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
new file mode 100644
index 0000000..a7abb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
@@ -0,0 +1,234 @@
+/*
+ * 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.util.concurrency
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import android.view.Choreographer
+import com.android.systemui.Dependency
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.BroadcastRunning
+import com.android.systemui.dagger.qualifiers.LongRunning
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.NotifInflation
+import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
+import javax.inject.Named
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class BackPanelUiThread
+
+/** Dagger Module for classes found within the concurrent package. */
+@Module
+object SysUIConcurrencyModule {
+    // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this
+    // thread
+    private const val BG_SLOW_DISPATCH_THRESHOLD = 1000L
+    private const val BG_SLOW_DELIVERY_THRESHOLD = 1000L
+    private const val LONG_SLOW_DISPATCH_THRESHOLD = 2500L
+    private const val LONG_SLOW_DELIVERY_THRESHOLD = 2500L
+    private const val BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L
+    private const val BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L
+    private const val NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L
+    private const val NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L
+
+    /** Background Looper */
+    @Provides
+    @SysUISingleton
+    @Background
+    fun provideBgLooper(): Looper {
+        val thread = HandlerThread("SysUiBg", Process.THREAD_PRIORITY_BACKGROUND)
+        thread.start()
+        thread
+            .getLooper()
+            .setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD, BG_SLOW_DELIVERY_THRESHOLD)
+        return thread.getLooper()
+    }
+
+    /** BroadcastRunning Looper (for sending and receiving broadcasts) */
+    @Provides
+    @SysUISingleton
+    @BroadcastRunning
+    fun provideBroadcastRunningLooper(): Looper {
+        val thread = HandlerThread("BroadcastRunning", Process.THREAD_PRIORITY_BACKGROUND)
+        thread.start()
+        thread
+            .getLooper()
+            .setSlowLogThresholdMs(
+                BROADCAST_SLOW_DISPATCH_THRESHOLD,
+                BROADCAST_SLOW_DELIVERY_THRESHOLD
+            )
+        return thread.getLooper()
+    }
+
+    /** Long running tasks Looper */
+    @Provides
+    @SysUISingleton
+    @LongRunning
+    fun provideLongRunningLooper(): Looper {
+        val thread = HandlerThread("SysUiLng", Process.THREAD_PRIORITY_BACKGROUND)
+        thread.start()
+        thread
+            .getLooper()
+            .setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD, LONG_SLOW_DELIVERY_THRESHOLD)
+        return thread.getLooper()
+    }
+
+    /** Notification inflation Looper */
+    @Provides
+    @SysUISingleton
+    @NotifInflation
+    fun provideNotifInflationLooper(@Background bgLooper: Looper): Looper {
+        if (!Flags.dedicatedNotifInflationThread()) {
+            return bgLooper
+        }
+        val thread = HandlerThread("NotifInflation", Process.THREAD_PRIORITY_BACKGROUND)
+        thread.start()
+        val looper = thread.getLooper()
+        looper.setSlowLogThresholdMs(
+            NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD,
+            NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD
+        )
+        return looper
+    }
+
+    @Provides
+    @SysUISingleton
+    @BackPanelUiThread
+    fun provideBackPanelUiThreadContext(
+        @Main mainLooper: Looper,
+        @Main mainHandler: Handler,
+        @Main mainExecutor: Executor
+    ): UiThreadContext {
+        return if (Flags.edgeBackGestureHandlerThread()) {
+            val thread =
+                HandlerThread("BackPanelUiThread", Process.THREAD_PRIORITY_DISPLAY).apply {
+                    start()
+                    looper.setSlowLogThresholdMs(
+                        LONG_SLOW_DISPATCH_THRESHOLD,
+                        LONG_SLOW_DELIVERY_THRESHOLD
+                    )
+                }
+            UiThreadContext(
+                thread.looper,
+                thread.threadHandler,
+                thread.threadExecutor,
+                thread.threadHandler.runWithScissors { Choreographer.getInstance() }
+            )
+        } else {
+            UiThreadContext(
+                mainLooper,
+                mainHandler,
+                mainExecutor,
+                mainHandler.runWithScissors { Choreographer.getInstance() }
+            )
+        }
+    }
+
+    /**
+     * Background Handler.
+     *
+     * Prefer the Background Executor when possible.
+     */
+    @Provides
+    @Background
+    fun provideBgHandler(@Background bgLooper: Looper): Handler = Handler(bgLooper)
+
+    /** Provide a BroadcastRunning Executor (for sending and receiving broadcasts). */
+    @Provides
+    @SysUISingleton
+    @BroadcastRunning
+    fun provideBroadcastRunningExecutor(@BroadcastRunning looper: Looper): Executor =
+        ExecutorImpl(looper)
+
+    /** Provide a Long running Executor. */
+    @Provides
+    @SysUISingleton
+    @LongRunning
+    fun provideLongRunningExecutor(@LongRunning looper: Looper): Executor = ExecutorImpl(looper)
+
+    /** Provide a Long running Executor. */
+    @Provides
+    @SysUISingleton
+    @LongRunning
+    fun provideLongRunningDelayableExecutor(@LongRunning looper: Looper): DelayableExecutor =
+        ExecutorImpl(looper)
+
+    /** Provide a Background-Thread Executor. */
+    @Provides
+    @SysUISingleton
+    @Background
+    fun provideBackgroundExecutor(@Background looper: Looper): Executor = ExecutorImpl(looper)
+
+    /** Provide a Background-Thread Executor. */
+    @Provides
+    @SysUISingleton
+    @Background
+    fun provideBackgroundDelayableExecutor(@Background looper: Looper): DelayableExecutor =
+        ExecutorImpl(looper)
+
+    /** Provide a Background-Thread Executor. */
+    @Provides
+    @SysUISingleton
+    @Background
+    fun provideBackgroundRepeatableExecutor(
+        @Background exec: DelayableExecutor
+    ): RepeatableExecutor = RepeatableExecutorImpl(exec)
+
+    /** Provide a Main-Thread Executor. */
+    @Provides
+    @SysUISingleton
+    @Main
+    fun provideMainRepeatableExecutor(@Main exec: DelayableExecutor): RepeatableExecutor =
+        RepeatableExecutorImpl(exec)
+
+    /**  */
+    @Provides
+    @Main
+    fun providesMainMessageRouter(@Main executor: DelayableExecutor): MessageRouter =
+        MessageRouterImpl(executor)
+
+    /**  */
+    @Provides
+    @Background
+    fun providesBackgroundMessageRouter(@Background executor: DelayableExecutor): MessageRouter =
+        MessageRouterImpl(executor)
+
+    /**  */
+    @Provides
+    @SysUISingleton
+    @Named(Dependency.TIME_TICK_HANDLER_NAME)
+    fun provideTimeTickHandler(): Handler {
+        val thread = HandlerThread("TimeTick")
+        thread.start()
+        return Handler(thread.getLooper())
+    }
+
+    /**  */
+    @Provides
+    @SysUISingleton
+    @NotifInflation
+    fun provideNotifInflationExecutor(@NotifInflation looper: Looper): Executor =
+        ExecutorImpl(looper)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt
new file mode 100644
index 0000000..8c8c686
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency
+
+import android.os.Handler
+import android.os.Looper
+import android.view.Choreographer
+import com.android.systemui.util.Assert
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicReference
+
+private const val DEFAULT_TIMEOUT = 150L
+
+class UiThreadContext(
+    val looper: Looper,
+    val handler: Handler,
+    val executor: Executor,
+    val choreographer: Choreographer
+) {
+    fun isCurrentThread() {
+        Assert.isCurrentThread(looper)
+    }
+
+    fun <T> runWithScissors(block: () -> T): T {
+        return handler.runWithScissors(block)
+    }
+
+    fun runWithScissors(block: Runnable) {
+        handler.runWithScissors(block, DEFAULT_TIMEOUT)
+    }
+}
+
+fun <T> Handler.runWithScissors(block: () -> T): T {
+    val returnedValue = AtomicReference<T>()
+    runWithScissors({ returnedValue.set(block()) }, DEFAULT_TIMEOUT)
+    return returnedValue.get()!!
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index c6b0dc5..e0f64b4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -18,8 +18,6 @@
 
 import android.bluetooth.BluetoothAdapter
 import android.media.AudioDeviceInfo
-import android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES
-import android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.media.BluetoothMediaDevice
@@ -81,30 +79,32 @@
     val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
 
     private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
-        if (type == TYPE_WIRED_HEADPHONES || type == TYPE_WIRED_HEADSET) {
+        if (
+            BluetoothAdapter.checkBluetoothAddress(address) &&
+                localBluetoothManager != null &&
+                bluetoothAdapter != null
+        ) {
+            val remoteDevice = bluetoothAdapter.getRemoteDevice(address)
+            localBluetoothManager.cachedDeviceManager.findDevice(remoteDevice)?.let {
+                device: CachedBluetoothDevice ->
+                return AudioOutputDevice.Bluetooth(
+                    name = device.name,
+                    icon = deviceIconInteractor.loadIcon(device),
+                    cachedBluetoothDevice = device,
+                )
+            }
+        }
+        // Built-in device has an empty address
+        if (address.isNotEmpty()) {
             return AudioOutputDevice.Wired(
                 name = productName.toString(),
                 icon = deviceIconInteractor.loadIcon(type),
             )
         }
-        val cachedBluetoothDevice: CachedBluetoothDevice? =
-            if (address.isEmpty() || localBluetoothManager == null || bluetoothAdapter == null) {
-                null
-            } else {
-                val remoteDevice = bluetoothAdapter.getRemoteDevice(address)
-                localBluetoothManager.cachedDeviceManager.findDevice(remoteDevice)
-            }
-        return cachedBluetoothDevice?.let {
-            AudioOutputDevice.Bluetooth(
-                name = it.name,
-                icon = deviceIconInteractor.loadIcon(it),
-                cachedBluetoothDevice = it,
-            )
-        }
-            ?: AudioOutputDevice.BuiltIn(
-                name = productName.toString(),
-                icon = deviceIconInteractor.loadIcon(type),
-            )
+        return AudioOutputDevice.BuiltIn(
+            name = productName.toString(),
+            icon = deviceIconInteractor.loadIcon(type),
+        )
     }
 
     private fun MediaDevice.toAudioOutputDevice(): AudioOutputDevice {
@@ -115,7 +115,8 @@
                     icon = icon,
                     cachedBluetoothDevice = cachedDevice,
                 )
-            deviceType == MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE ->
+            deviceType == MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE ||
+                deviceType == MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE ->
                 AudioOutputDevice.Wired(
                     name = name,
                     icon = icon,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 0dc2647..79a4ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -19,14 +19,13 @@
 import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
 import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.util.LocalMediaManagerFactory
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 
 interface LocalMediaRepositoryFactory {
 
-    fun create(packageName: String?): LocalMediaRepository
+    fun create(packageName: String?, coroutineScope: CoroutineScope): LocalMediaRepository
 }
 
 @SysUISingleton
@@ -35,10 +34,12 @@
 constructor(
     private val eventsReceiver: AudioManagerEventsReceiver,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
-    @Application private val coroutineScope: CoroutineScope,
 ) : LocalMediaRepositoryFactory {
 
-    override fun create(packageName: String?): LocalMediaRepository =
+    override fun create(
+        packageName: String?,
+        coroutineScope: CoroutineScope
+    ): LocalMediaRepository =
         LocalMediaRepositoryImpl(
             eventsReceiver,
             localMediaManagerFactory.create(packageName),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 9fbd79a..31a8977 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -35,6 +35,7 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -46,6 +47,7 @@
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.withContext
 
 /** Provides observable models about the current media session state. */
@@ -105,12 +107,9 @@
             .filterData()
             .map { it?.packageName }
             .distinctUntilChanged()
-            .map { localMediaRepositoryFactory.create(it) }
-            .stateIn(
-                coroutineScope,
-                SharingStarted.Eagerly,
-                localMediaRepositoryFactory.create(null)
-            )
+            .transformLatest {
+                coroutineScope { emit(localMediaRepositoryFactory.create(it, this)) }
+            }
 
     /** Currently connected [MediaDevice]. */
     val currentConnectedDevice: Flow<MediaDevice?> =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index fd01b48..850162e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -146,14 +146,18 @@
             isEnabled = isEnabled,
             a11yStep = volumeRange.step,
             a11yClickDescription =
-                context.getString(
-                    if (isMuted) {
-                        R.string.volume_panel_hint_unmute
-                    } else {
-                        R.string.volume_panel_hint_mute
-                    },
-                    label,
-                ),
+                if (isAffectedByMute) {
+                    context.getString(
+                        if (isMuted) {
+                            R.string.volume_panel_hint_unmute
+                        } else {
+                            R.string.volume_panel_hint_mute
+                        },
+                        label,
+                    )
+                } else {
+                    null
+                },
             a11yStateDescription =
                 if (volume == volumeRange.first) {
                     context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
index 99f95648..3da725b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -98,12 +98,12 @@
 
     private fun showNewVolumePanel() {
         activityStarter.dismissKeyguardThenExecute(
-            {
+            /* action = */ {
                 volumePanelGlobalStateInteractor.setVisible(true)
                 false
             },
-            {},
-            true
+            /* cancel = */ {},
+            /* afterKeyguardGone = */ true,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 63426a4..9a99ed7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -128,6 +128,12 @@
     private lateinit var packageManager: PackageManager
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
 
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var displayStateInteractor: DisplayStateInteractor
+    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+    private lateinit var iconProvider: IconProvider
+
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val biometricPromptRepository = FakePromptRepository()
@@ -143,17 +149,12 @@
     private val promptSelectorInteractor by lazy {
         PromptSelectorInteractorImpl(
             fingerprintRepository,
+            displayStateInteractor,
             biometricPromptRepository,
             lockPatternUtils,
         )
     }
 
-    private lateinit var displayRepository: FakeDisplayRepository
-    private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
-    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
-    private lateinit var iconProvider: IconProvider
-
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
     private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 3102a84..6e78e33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -25,13 +25,16 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.shared.model.BiometricModalities
+import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -75,12 +78,30 @@
     private val promptRepository = FakePromptRepository()
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
 
+    private lateinit var displayStateRepository: FakeDisplayStateRepository
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var displayStateInteractor: DisplayStateInteractor
     private lateinit var interactor: PromptSelectorInteractor
 
     @Before
     fun setup() {
+        displayStateRepository = FakeDisplayStateRepository()
+        displayRepository = FakeDisplayRepository()
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                displayStateRepository,
+                displayRepository,
+            )
         interactor =
-            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
+            PromptSelectorInteractorImpl(
+                fingerprintRepository,
+                displayStateInteractor,
+                promptRepository,
+                lockPatternUtils
+            )
     }
 
     private fun basicPromptInfo() =
@@ -155,7 +176,8 @@
             modalities,
             CHALLENGE,
             OP_PACKAGE_NAME,
-            false /*onSwitchToCredential*/
+            onSwitchToCredential = false,
+            isLandscape = false,
         )
 
         assertThat(currentPrompt).isNotNull()
@@ -200,22 +222,49 @@
     fun promptKind_isBiometric_whenBiometricAllowed() =
         testScope.runTest {
             setUserCredentialType(isPassword = true)
-            val info = basicPromptInfo()
 
             val promptKind by collectLastValue(interactor.promptKind)
             assertThat(promptKind).isEqualTo(PromptKind.None)
 
-            interactor.setPrompt(
-                info,
-                USER_ID,
-                REQUEST_ID,
-                modalities,
-                CHALLENGE,
-                OP_PACKAGE_NAME,
-                false /*onSwitchToCredential*/
-            )
+            setPrompt()
 
-            assertThat(promptKind?.isBiometric()).isTrue()
+            assertThat(promptKind?.isOnePanePortraitBiometric()).isTrue()
+
+            interactor.resetPrompt(REQUEST_ID)
+            verifyUnset()
+        }
+
+    @Test
+    fun promptKind_isBiometricTwoPane_whenBiometricAllowed_landscape() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            displayStateRepository.setIsLargeScreen(false)
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            setPrompt()
+
+            assertThat(promptKind?.isTwoPaneLandscapeBiometric()).isTrue()
+
+            interactor.resetPrompt(REQUEST_ID)
+            verifyUnset()
+        }
+
+    @Test
+    fun promptKind_isBiometricOnePane_whenBiometricAllowed_largeScreenLandscape() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            displayStateRepository.setIsLargeScreen(true)
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            setPrompt()
+
+            assertThat(promptKind?.isOnePaneLargeScreenLandscapeBiometric()).isTrue()
 
             interactor.resetPrompt(REQUEST_ID)
             verifyUnset()
@@ -225,20 +274,11 @@
     fun promptKind_isCredential_onSwitchToCredential() =
         testScope.runTest {
             setUserCredentialType(isPassword = true)
-            val info = basicPromptInfo()
 
             val promptKind by collectLastValue(interactor.promptKind)
             assertThat(promptKind).isEqualTo(PromptKind.None)
 
-            interactor.setPrompt(
-                info,
-                USER_ID,
-                REQUEST_ID,
-                modalities,
-                CHALLENGE,
-                OP_PACKAGE_NAME,
-                true /*onSwitchToCredential*/
-            )
+            setPrompt(onSwitchToCredential = true)
 
             assertThat(promptKind).isEqualTo(PromptKind.Password)
 
@@ -259,15 +299,7 @@
             val promptKind by collectLastValue(interactor.promptKind)
             assertThat(promptKind).isEqualTo(PromptKind.None)
 
-            interactor.setPrompt(
-                info,
-                USER_ID,
-                REQUEST_ID,
-                modalities,
-                CHALLENGE,
-                OP_PACKAGE_NAME,
-                false /*onSwitchToCredential*/
-            )
+            setPrompt(info)
 
             assertThat(promptKind).isEqualTo(PromptKind.Password)
 
@@ -292,15 +324,7 @@
             val promptKind by collectLastValue(interactor.promptKind)
             assertThat(promptKind).isEqualTo(PromptKind.None)
 
-            interactor.setPrompt(
-                info,
-                USER_ID,
-                REQUEST_ID,
-                modalities,
-                CHALLENGE,
-                OP_PACKAGE_NAME,
-                false /*onSwitchToCredential*/
-            )
+            setPrompt(info)
 
             assertThat(promptKind).isEqualTo(PromptKind.Password)
 
@@ -312,6 +336,7 @@
     fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() =
         testScope.runTest {
             setUserCredentialType(isPassword = true)
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
             val info =
                 basicPromptInfo().apply {
                     isDeviceCredentialAllowed = true
@@ -322,22 +347,32 @@
             val promptKind by collectLastValue(interactor.promptKind)
             assertThat(promptKind).isEqualTo(PromptKind.None)
 
-            interactor.setPrompt(
-                info,
-                USER_ID,
-                REQUEST_ID,
-                modalities,
-                CHALLENGE,
-                OP_PACKAGE_NAME,
-                false /*onSwitchToCredential*/
-            )
+            setPrompt(info)
 
-            assertThat(promptKind?.isBiometric()).isTrue()
+            assertThat(promptKind?.isOnePaneNoSensorLandscapeBiometric()).isTrue()
 
             interactor.resetPrompt(REQUEST_ID)
             verifyUnset()
         }
 
+    private fun setPrompt(
+        info: PromptInfo = basicPromptInfo(),
+        onSwitchToCredential: Boolean = false
+    ) {
+        interactor.setPrompt(
+            info,
+            USER_ID,
+            REQUEST_ID,
+            modalities,
+            CHALLENGE,
+            OP_PACKAGE_NAME,
+            onSwitchToCredential = onSwitchToCredential,
+            isLandscape =
+                displayStateRepository.currentRotation.value == DisplayRotation.ROTATION_90 ||
+                    displayStateRepository.currentRotation.value == DisplayRotation.ROTATION_270,
+        )
+    }
+
     private fun TestScope.useCredentialAndReset(kind: PromptKind) {
         setUserCredentialType(
             isPin = kind == PromptKind.Pin,
@@ -366,7 +401,8 @@
             BiometricModalities(),
             CHALLENGE,
             OP_PACKAGE_NAME,
-            false /*onSwitchToCredential*/
+            onSwitchToCredential = false,
+            isLandscape = false,
         )
 
         // not using biometrics, should be null with no fallback option
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index c177511..f46cfdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -383,11 +383,25 @@
             }
 
             if (testCase.isFaceOnly) {
-                val expectedIconAsset = R.raw.face_dialog_authenticating
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                val expectedIconAsset =
+                    if (shouldPulseAnimation!!) {
+                        if (lastPulseLightToDark!!) {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        } else {
+                            R.drawable.face_dialog_pulse_light_to_dark
+                        }
+                    } else {
+                        R.drawable.face_dialog_pulse_dark_to_light
+                    }
                 assertThat(iconAsset).isEqualTo(expectedIconAsset)
                 assertThat(iconContentDescriptionId)
                     .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
                 assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(true)
             }
 
             if (testCase.isCoex) {
@@ -409,11 +423,26 @@
                     }
                 } else {
                     // implicit flow
-                    val expectedIconAsset = R.raw.face_dialog_authenticating
+                    val shouldRepeatAnimation by
+                        collectLastValue(iconViewModel.shouldRepeatAnimation)
+                    val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                    val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                    val expectedIconAsset =
+                        if (shouldPulseAnimation!!) {
+                            if (lastPulseLightToDark!!) {
+                                R.drawable.face_dialog_pulse_dark_to_light
+                            } else {
+                                R.drawable.face_dialog_pulse_light_to_dark
+                            }
+                        } else {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        }
                     assertThat(iconAsset).isEqualTo(expectedIconAsset)
                     assertThat(iconContentDescriptionId)
                         .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
                     assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldRepeatAnimation).isEqualTo(true)
                 }
             }
         }
@@ -503,9 +532,14 @@
         }
 
         if (testCase.isFaceOnly) {
+            val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+            val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+            assertThat(shouldPulseAnimation!!).isEqualTo(false)
             assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
             assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
             assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
 
             // Clear error, go to idle
             errorJob.join()
@@ -514,6 +548,7 @@
             assertThat(iconContentDescriptionId)
                 .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
             assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
         }
 
         if (testCase.isCoex) {
@@ -596,10 +631,15 @@
 
             // If co-ex, using implicit flow (explicit flow always requires confirmation)
             if (testCase.isFaceOnly || testCase.isCoex) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
                 assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
                 assertThat(iconContentDescriptionId)
                     .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                 assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
             }
         }
     }
@@ -621,10 +661,15 @@
             )
 
             if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
                 assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
                 assertThat(iconContentDescriptionId)
                     .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                 assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
             }
 
             // explicit flow because confirmation requested
@@ -666,10 +711,15 @@
             viewModel.confirmAuthenticated()
 
             if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
                 assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
                 assertThat(iconContentDescriptionId)
                     .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
                 assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
             }
 
             // explicit flow because confirmation requested
@@ -1407,7 +1457,12 @@
         runningTaskInfo.topActivity = topActivity
         whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo))
         selector =
-            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
+            PromptSelectorInteractorImpl(
+                fingerprintRepository,
+                displayStateInteractor,
+                promptRepository,
+                lockPatternUtils
+            )
         selector.resetPrompt(REQUEST_ID)
 
         viewModel =
@@ -1643,7 +1698,8 @@
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
         CHALLENGE,
         packageName,
-        false /*onUseDeviceCredential*/
+        onSwitchToCredential = false,
+        isLandscape = false,
     )
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 8a12a90..c1e3e84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
@@ -289,6 +290,7 @@
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
+                applicationScope = kosmos.applicationCoroutineScope,
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 3bf4173..3906c40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -191,7 +191,7 @@
 
     @Before
     fun setup() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         staticMockSession =
             ExtendedMockito.mockitoSession()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 5f7c386..20fb701 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -29,7 +29,9 @@
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 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.kosmos.testScope
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -49,7 +51,6 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.animation.UniqueObjectHostView
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.settings.FakeSettings
@@ -75,6 +76,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -112,12 +115,14 @@
     private val testScope = kosmos.testScope
     private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
+    private lateinit var shadeExpansion: MutableStateFlow<Float>
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
     private val settings = FakeSettings()
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeHandler: FakeHandler
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
 
     @Before
     fun setup() {
@@ -129,7 +134,9 @@
         fakeHandler = FakeHandler(testableLooper.looper)
         whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
         isQsBypassingShade = MutableStateFlow(false)
+        shadeExpansion = MutableStateFlow(0f)
         whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
+        whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion)
         whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
         mediaHierarchyManager =
             MediaHierarchyManager(
@@ -141,6 +148,7 @@
                 mediaDataManager,
                 keyguardViewController,
                 dreamOverlayStateController,
+                kosmos.keyguardInteractor,
                 kosmos.communalTransitionViewModel,
                 configurationController,
                 wakefulnessLifecycle,
@@ -191,7 +199,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -204,7 +212,7 @@
         verify(mediaCarouselController, times(0))
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -218,7 +226,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -231,7 +239,7 @@
         verify(mediaCarouselController, times(0))
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -245,7 +253,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -255,7 +263,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -269,7 +277,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -281,7 +289,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 ArgumentMatchers.anyInt(),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 anyBoolean(),
                 anyLong(),
                 anyLong()
@@ -297,7 +305,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 eq(MediaHierarchyManager.LOCATION_QQS),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 eq(false),
                 anyLong(),
                 anyLong()
@@ -309,7 +317,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 eq(false),
                 anyLong(),
                 anyLong()
@@ -482,6 +490,26 @@
     }
 
     @Test
+    fun isCurrentlyInGuidedTransformation_desiredLocationIsHub_returnsFalse() =
+        testScope.runTest {
+            goToLockscreen()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+            )
+            mediaHierarchyManager.qsExpansion = 0f
+            mediaHierarchyManager.setTransitionToFullShadeAmount(123f)
+
+            whenever(lockHost.visible).thenReturn(true)
+            whenever(qsHost.visible).thenReturn(true)
+            whenever(qqsHost.visible).thenReturn(true)
+            whenever(hubModeHost.visible).thenReturn(true)
+
+            assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+        }
+
+    @Test
     fun testDream() {
         goToDream()
         setMediaDreamComplicationEnabled(true)
@@ -499,7 +527,7 @@
         verify(mediaCarouselController)
             .onDesiredLocationChanged(
                 eq(MediaHierarchyManager.LOCATION_QQS),
-                any(MediaHostState::class.java),
+                any<MediaHostState>(),
                 eq(false),
                 anyLong(),
                 anyLong()
@@ -532,7 +560,7 @@
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
                     eq(MediaHierarchyManager.LOCATION_QQS),
-                    any(MediaHostState::class.java),
+                    any<MediaHostState>(),
                     eq(false),
                     anyLong(),
                     anyLong()
@@ -590,7 +618,50 @@
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
                     eq(MediaHierarchyManager.LOCATION_QS),
-                    any(MediaHostState::class.java),
+                    any<MediaHostState>(),
+                    eq(false),
+                    anyLong(),
+                    anyLong()
+                )
+        }
+
+    @Test
+    fun testCommunalLocation_whenDreamingAndShadeExpanding() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(true)
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+            )
+            // Mock the behavior for dreaming that pulling down shade will immediately set QS as
+            // expanded
+            expandQS()
+            // Starts opening the shade
+            shadeExpansion.value = 0.1f
+            runCurrent()
+
+            // UMO shows on hub
+            verify(mediaCarouselController)
+                .onDesiredLocationChanged(
+                    eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+                    anyOrNull(),
+                    eq(false),
+                    anyLong(),
+                    anyLong()
+                )
+            clearInvocations(mediaCarouselController)
+
+            // The shade is opened enough to make QS elements visible
+            shadeExpansion.value = 0.5f
+            runCurrent()
+
+            // UMO shows on QS
+            verify(mediaCarouselController)
+                .onDesiredLocationChanged(
+                    eq(MediaHierarchyManager.LOCATION_QS),
+                    any<MediaHostState>(),
                     eq(false),
                     anyLong(),
                     anyLong()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index e5d3082..80ebe56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -376,7 +376,7 @@
 
     @Test
     fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -388,7 +388,7 @@
 
     @Test
     fun attachPlayer_seekBarEnabled_seekBarVisible() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -399,7 +399,7 @@
 
     @Test
     fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -415,7 +415,7 @@
 
     @Test
     fun attachPlayer_notScrubbing_scrubbingViewsGone() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.canShowScrubbingTime = true
@@ -435,7 +435,7 @@
 
     @Test
     fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.canShowScrubbingTime = false
@@ -454,7 +454,7 @@
 
     @Test
     fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(true)
@@ -476,7 +476,7 @@
 
     @Test
     fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(false)
@@ -498,7 +498,7 @@
 
     @Test
     fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(true)
@@ -524,7 +524,7 @@
 
     @Test
     fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
-        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
 
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index f1c97dd..23cf7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -55,6 +55,7 @@
     companion object {
         private const val START_X: Float = 0f
     }
+
     private val kosmos = testKosmos()
     private lateinit var mBackPanelController: BackPanelController
     private lateinit var systemClock: FakeSystemClock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 6956bea..09d6a1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -39,7 +39,6 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -51,6 +50,7 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.compose.ui.platform.ComposeView;
 import androidx.lifecycle.Lifecycle;
 import androidx.test.filters.SmallTest;
 
@@ -58,12 +58,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSComponent;
 import com.android.systemui.qs.external.TileServiceRequestController;
-import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
@@ -112,9 +110,7 @@
     @Mock private QSSquishinessController mSquishinessController;
     @Mock private FooterActionsViewModel mFooterActionsViewModel;
     @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
-    @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    @Mock private FeatureFlagsClassic mFeatureFlags;
     private ViewGroup mQsView;
 
     private final CommandQueue mCommandQueue =
@@ -259,6 +255,39 @@
     }
 
     @Test
+    public void setQsExpansion_whenShouldUpdateSquishinessTrue_setsSquishinessBasedOnFraction() {
+        enableSplitShade();
+        when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+        float expansion = 0.456f;
+        float panelExpansionFraction = 0.678f;
+        float proposedTranslation = 567f;
+        float squishinessFraction = 0.789f;
+
+        mUnderTest.setShouldUpdateSquishinessOnMedia(true);
+        mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+                squishinessFraction);
+
+        verify(mQSMediaHost).setSquishFraction(squishinessFraction);
+    }
+
+    @Test
+    public void setQsExpansion_whenOnKeyguardAndShouldUpdateSquishinessFalse_setsSquishiness() {
+        // Random test values without any meaning. They just have to be different from each other.
+        float expansion = 0.123f;
+        float panelExpansionFraction = 0.321f;
+        float proposedTranslation = 456f;
+        float squishinessFraction = 0.567f;
+
+        enableSplitShade();
+        setStatusBarCurrentAndUpcomingState(KEYGUARD);
+        mUnderTest.setShouldUpdateSquishinessOnMedia(false);
+        mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+                squishinessFraction);
+
+        verify(mQSMediaHost).setSquishFraction(1.0f);
+    }
+
+    @Test
     public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
         // Random test values without any meaning. They just have to be different from each other.
         float expansion = 0.123f;
@@ -496,18 +525,13 @@
     @Test
     @EnableSceneContainer
     public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
-        clearInvocations(
-                mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
+        clearInvocations(mFooterActionsViewModel, mFooterActionsViewModelFactory);
         QSImpl other = instantiate();
 
         other.onComponentCreated(mQsComponent, null);
 
         assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull();
-        verifyZeroInteractions(
-                mFooterActionsViewModel,
-                mFooterActionsViewBinder,
-                mFooterActionsViewModelFactory
-        );
+        verifyZeroInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory);
     }
 
     @Test
@@ -553,9 +577,7 @@
                 mock(QSLogger.class),
                 mock(FooterActionsController.class),
                 mFooterActionsViewModelFactory,
-                mFooterActionsViewBinder,
-                mLargeScreenShadeInterpolator,
-                mFeatureFlags
+                mLargeScreenShadeInterpolator
         );
     }
 
@@ -589,41 +611,20 @@
         customizer.setId(android.R.id.edit);
         mQsView.addView(customizer);
 
-        View footerActionsView = new FooterActionsViewBinder().create(mContext);
+        ComposeView footerActionsView = new ComposeView(mContext);
         footerActionsView.setId(R.id.qs_footer_actions);
         mQsView.addView(footerActionsView);
     }
 
     private void setUpInflater() {
-        LayoutInflater realInflater = LayoutInflater.from(mContext);
-
         when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater);
         when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean()))
-                .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
-                        (ViewGroup) invocation.getArgument(1),
-                        (boolean) invocation.getArgument(2)));
+                .thenAnswer((invocation) -> mQsView);
         when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class)))
-                .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
-                        (ViewGroup) invocation.getArgument(1)));
+                .thenAnswer((invocation) -> mQsView);
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater);
     }
 
-    private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) {
-        return inflate(realInflater, layoutRes, root, root != null);
-    }
-
-    private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root,
-            boolean attachToRoot) {
-        if (layoutRes == R.layout.footer_actions
-                || layoutRes == R.layout.footer_actions_text_button
-                || layoutRes == R.layout.footer_actions_number_button
-                || layoutRes == R.layout.footer_actions_icon_button) {
-            return realInflater.inflate(layoutRes, root, attachToRoot);
-        }
-
-        return mQsView;
-    }
-
     private void setupQsComponent() {
         when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController);
         when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index 2da4b72..87031d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -31,9 +31,6 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -46,22 +43,21 @@
 class GridConsistencyInteractorTest : SysuiTestCase() {
 
     private val iconOnlyTiles =
-        MutableStateFlow(
-            setOf(
-                TileSpec.create("smallA"),
-                TileSpec.create("smallB"),
-                TileSpec.create("smallC"),
-                TileSpec.create("smallD"),
-                TileSpec.create("smallE"),
-            )
+        setOf(
+            TileSpec.create("smallA"),
+            TileSpec.create("smallB"),
+            TileSpec.create("smallC"),
+            TileSpec.create("smallD"),
+            TileSpec.create("smallE"),
         )
 
     private val kosmos =
         testKosmos().apply {
             iconTilesRepository =
                 object : IconTilesRepository {
-                    override val iconTilesSpecs: StateFlow<Set<TileSpec>>
-                        get() = iconOnlyTiles.asStateFlow()
+                    override fun isIconTile(spec: TileSpec): Boolean {
+                        return iconOnlyTiles.contains(spec)
+                    }
                 }
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
index bda48ad..1eb6d63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -25,9 +25,6 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,21 +34,20 @@
 class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
 
     private val iconOnlyTiles =
-        MutableStateFlow(
-            setOf(
-                TileSpec.create("smallA"),
-                TileSpec.create("smallB"),
-                TileSpec.create("smallC"),
-                TileSpec.create("smallD"),
-                TileSpec.create("smallE"),
-            )
+        setOf(
+            TileSpec.create("smallA"),
+            TileSpec.create("smallB"),
+            TileSpec.create("smallC"),
+            TileSpec.create("smallD"),
+            TileSpec.create("smallE"),
         )
     private val kosmos =
         testKosmos().apply {
             iconTilesRepository =
                 object : IconTilesRepository {
-                    override val iconTilesSpecs: StateFlow<Set<TileSpec>>
-                        get() = iconOnlyTiles.asStateFlow()
+                    override fun isIconTile(spec: TileSpec): Boolean {
+                        return iconOnlyTiles.contains(spec)
+                    }
                 }
         }
     private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index bde1445..b8267a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -18,6 +18,8 @@
 
 import android.graphics.Rect
 import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -30,6 +32,7 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.ambient.touch.TouchHandler
 import com.android.systemui.ambient.touch.TouchMonitor
@@ -51,6 +54,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
@@ -64,9 +68,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.Mock
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @ExperimentalCoroutinesApi
@@ -124,6 +130,7 @@
                     ambientTouchComponentFactory,
                     communalContent,
                     kosmos.sceneDataSourceDelegator,
+                    kosmos.notificationStackScrollLayoutController
                 )
         }
         testableLooper = TestableLooper.get(this)
@@ -166,6 +173,7 @@
                         ambientTouchComponentFactory,
                         communalContent,
                         kosmos.sceneDataSourceDelegator,
+                        kosmos.notificationStackScrollLayoutController
                     )
 
                 // First call succeeds.
@@ -176,6 +184,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_communalClosed_doesNotIntercept() =
         with(kosmos) {
@@ -187,6 +196,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_openGesture_interceptsTouches() =
         with(kosmos) {
@@ -204,6 +214,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_communalTransitioning_interceptsTouches() =
         with(kosmos) {
@@ -230,6 +241,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_communalOpen_interceptsTouches() =
         with(kosmos) {
@@ -244,6 +256,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() =
         with(kosmos) {
@@ -262,6 +275,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() =
         with(kosmos) {
@@ -278,6 +292,7 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
     @Test
     fun onTouchEvent_containerViewDisposed_doesNotIntercept() =
         with(kosmos) {
@@ -310,6 +325,7 @@
                     ambientTouchComponentFactory,
                     communalContent,
                     kosmos.sceneDataSourceDelegator,
+                    kosmos.notificationStackScrollLayoutController,
                 )
 
             assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -329,6 +345,7 @@
                     ambientTouchComponentFactory,
                     communalContent,
                     kosmos.sceneDataSourceDelegator,
+                    kosmos.notificationStackScrollLayoutController,
                 )
 
             // Only initView without attaching a view as we don't want the flows to start collecting
@@ -499,13 +516,30 @@
             }
         }
 
+    @Test
+    @EnableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
+    fun fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is closed.
+                goToScene(CommunalScenes.Blank)
+                `when`(
+                        notificationStackScrollLayoutController.isBelowLastNotification(
+                            anyFloat(),
+                            anyFloat()
+                        )
+                    )
+                    .thenReturn(false)
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+            }
+        }
+
     private fun initAndAttachContainerView() {
         containerView = View(context)
 
         parentView = FrameLayout(context)
-        parentView.addView(containerView)
 
-        underTest.initView(containerView)
+        parentView.addView(underTest.initView(containerView))
 
         // Attach the view so that flows start collecting.
         ViewUtils.attachView(parentView, CONTAINER_WIDTH, CONTAINER_HEIGHT)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 041adea..c3cedf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -837,6 +837,7 @@
                 mJavaAdapter,
                 mCastController,
                 new ResourcesSplitShadeStateController(),
+                () -> mKosmos.getCommunalTransitionViewModel(),
                 () -> mLargeScreenHeaderHelper
         );
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 845744a..85541aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -308,6 +308,7 @@
                 new JavaAdapter(mTestScope.getBackgroundScope()),
                 mCastController,
                 splitShadeStateController,
+                () -> mKosmos.getCommunalTransitionViewModel(),
                 () -> mLargeScreenHeaderHelper
         );
         mQsController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 7903a73..e984200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -91,7 +91,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -110,7 +111,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldNotHeadsUp(
@@ -129,7 +131,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -146,7 +149,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -163,7 +167,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -180,7 +185,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -197,7 +203,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             assertFsiNotSuppressed()
         }
@@ -208,7 +215,8 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -232,7 +240,8 @@
         ).thenReturn(PERMISSION_GRANTED)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+                    uiEventLogger)
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index 9765d53..53285eb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -23,7 +23,6 @@
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -40,7 +39,7 @@
     override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow()
 
     private val _isLargeScreen = MutableStateFlow<Boolean>(false)
-    override val isLargeScreen: Flow<Boolean> = _isLargeScreen.asStateFlow()
+    override val isLargeScreen: StateFlow<Boolean> = _isLargeScreen.asStateFlow()
 
     override val isReverseDefaultRotation = false
 
@@ -55,6 +54,10 @@
     fun setCurrentDisplaySize(size: Size) {
         _currentDisplaySize.value = size
     }
+
+    fun setIsLargeScreen(isLargeScreen: Boolean) {
+        _isLargeScreen.value = isLargeScreen
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
index 7f9a71c..56297f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
@@ -25,6 +25,7 @@
 val Kosmos.promptSelectorInteractor by Fixture {
     PromptSelectorInteractorImpl(
         fingerprintPropertyRepository = fingerprintPropertyRepository,
+        displayStateInteractor = displayStateInteractor,
         promptRepository = promptRepository,
         lockPatternUtils = lockPatternUtils
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
index a05b5e6..ad5242e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
@@ -19,7 +19,7 @@
 import android.hardware.display.BrightnessInfo
 import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
 import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
-import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
index 22784e4..0e84273 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
@@ -18,6 +18,15 @@
 
 import com.android.systemui.brightness.data.repository.screenBrightnessRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.mockito.mock
 
 val Kosmos.screenBrightnessInteractor by
-    Kosmos.Fixture { ScreenBrightnessInteractor(screenBrightnessRepository) }
+    Kosmos.Fixture {
+        ScreenBrightnessInteractor(
+            screenBrightnessRepository,
+            applicationCoroutineScope,
+            mock<TableLogBuffer>(),
+        )
+    }
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 b862078..0b28e3f 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
@@ -29,6 +29,7 @@
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
@@ -87,6 +88,7 @@
     val configurationInteractor by lazy { kosmos.configurationInteractor }
     val bouncerRepository by lazy { kosmos.bouncerRepository }
     val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
+    val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
     val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
     val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 604c16f..5ff44e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.qs.pipeline.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.android.systemui.retail.data.repository.RetailModeRepository
 
 /** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */
 var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) }
@@ -46,3 +48,6 @@
 val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() }
 var Kosmos.customTileAddedRepository: CustomTileAddedRepository by
     Kosmos.Fixture { fakeCustomTileAddedRepository }
+
+val Kosmos.fakeRetailModeRepository by Kosmos.Fixture { FakeRetailModeRepository() }
+var Kosmos.retailModeRepository: RetailModeRepository by Kosmos.Fixture { fakeRetailModeRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index b870039..d97a5b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.retailModeRepository
 import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
 import com.android.systemui.qs.pipeline.shared.logging.qsLogger
 import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
@@ -39,6 +40,7 @@
             installedTilesRepository,
             userRepository,
             minimumTilesRepository,
+            retailModeRepository,
             customTileStatePersister,
             { newQSTileFactory },
             qsTileFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt
index 0854e93..569429f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt
@@ -13,14 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.dreams.homecontrols
 
-import android.app.Activity
-import android.service.dreams.DreamService
-import javax.inject.Inject
+package com.android.systemui.statusbar.notification.stack
 
-class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
-    override fun getActivity(dreamService: DreamService): Activity {
-        return dreamService.activity
-    }
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notificationStackScrollLayoutController by
+    Kosmos.Fixture { mock<NotificationStackScrollLayoutController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt
index 3ac7129..15ef26d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt
@@ -33,19 +33,22 @@
         )
     }
 
-    fun wiredDevice(deviceName: String = "wired"): AudioDeviceInfo {
+    fun wiredDevice(
+        deviceName: String = "wired",
+        deviceAddress: String = "card=1;device=0",
+    ): AudioDeviceInfo {
         return AudioDeviceInfo(
             AudioDevicePort.createForTesting(
                 AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
                 deviceName,
-                "",
+                deviceAddress,
             )
         )
     }
 
     fun bluetoothDevice(
         deviceName: String = "bt",
-        deviceAddress: String = "test_address",
+        deviceAddress: String = "00:43:A8:23:10:F0",
     ): AudioDeviceInfo {
         return AudioDeviceInfo(
             AudioDevicePort.createForTesting(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
index 9c902cf..680535d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.mediaoutput.data.repository
 
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import kotlinx.coroutines.CoroutineScope
 
 class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMediaRepository) :
     LocalMediaRepositoryFactory {
@@ -27,6 +28,8 @@
         repositories[packageName] = localMediaRepository
     }
 
-    override fun create(packageName: String?): LocalMediaRepository =
-        repositories[packageName] ?: defaultProvider()
+    override fun create(
+        packageName: String?,
+        coroutineScope: CoroutineScope
+    ): LocalMediaRepository = repositories[packageName] ?: defaultProvider()
 }
diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS
deleted file mode 100644
index c66443f..0000000
--- a/packages/services/VirtualCamera/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-include /services/companion/java/com/android/server/companion/virtual/OWNERS
-caen@google.com
-jsebechlebsky@google.com
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 42f168b..77decb6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1379,6 +1379,30 @@
         }
     }
 
+    @RequiresNoPermission
+    @Override
+    public boolean isMagnificationSystemUIConnected() {
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("isMagnificationSystemUIConnected", "");
+        }
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return false;
+            }
+            if (!mSecurityPolicy.canControlMagnification(this)) {
+                return false;
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                MagnificationProcessor magnificationProcessor =
+                        mSystemSupport.getMagnificationProcessor();
+                return magnificationProcessor.isMagnificationSystemUIConnected();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
     public boolean isMagnificationCallbackEnabled(int displayId) {
         return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
     }
@@ -1925,6 +1949,11 @@
                 InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
     }
 
+    public void notifyMagnificationSystemUIConnectionChangedLocked(boolean connected) {
+        mInvocationHandler
+                .notifyMagnificationSystemUIConnectionChangedLocked(connected);
+    }
+
     public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
             @NonNull MagnificationConfig config) {
         mInvocationHandler
@@ -1976,6 +2005,21 @@
         return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
     }
 
+    /**
+     * Called by the invocation handler to notify the service that the
+     * magnification systemui connection has changed.
+     */
+    private void notifyMagnificationSystemUIConnectionChangedInternal(boolean connected) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onMagnificationSystemUIConnectionChanged(connected);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error sending magnification sysui connection changes to " + mService, re);
+            }
+        }
+    }
 
     /**
      * Called by the invocation handler to notify the service that the
@@ -2372,6 +2416,7 @@
         private static final int MSG_BIND_INPUT = 12;
         private static final int MSG_UNBIND_INPUT = 13;
         private static final int MSG_START_INPUT = 14;
+        private static final int MSG_ON_MAGNIFICATION_SYSTEM_UI_CONNECTION_CHANGED = 15;
 
         /** List of magnification callback states, mapping from displayId -> Boolean */
         @GuardedBy("mlock")
@@ -2398,6 +2443,13 @@
                     notifyClearAccessibilityCacheInternal();
                 } break;
 
+                case MSG_ON_MAGNIFICATION_SYSTEM_UI_CONNECTION_CHANGED: {
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final boolean connected = args.argi1 == 1;
+                    notifyMagnificationSystemUIConnectionChangedInternal(connected);
+                    args.recycle();
+                } break;
+
                 case MSG_ON_MAGNIFICATION_CHANGED: {
                     final SomeArgs args = (SomeArgs) message.obj;
                     final Region region = (Region) args.arg1;
@@ -2455,6 +2507,15 @@
             }
         }
 
+        public void notifyMagnificationSystemUIConnectionChangedLocked(boolean connected) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = connected ? 1 : 0;
+
+            final Message msg =
+                    obtainMessage(MSG_ON_MAGNIFICATION_SYSTEM_UI_CONNECTION_CHANGED, args);
+            msg.sendToTarget();
+        }
+
         public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
                 @NonNull MagnificationConfig config) {
             synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 20b727c..fe08338 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1812,6 +1812,17 @@
     }
 
     /**
+     * Called by the MagnificationController when the magnification systemui connection changes.
+     *
+     * @param connected Whether the connection is ready.
+     */
+    public void notifyMagnificationSystemUIConnectionChanged(boolean connected) {
+        synchronized (mLock) {
+            notifyMagnificationSystemUIConnectionChangedLocked(connected);
+        }
+    }
+
+    /**
      * Called by the MagnificationController when the state of display
      * magnification changes.
      *
@@ -2243,6 +2254,14 @@
         mProxyManager.clearCacheLocked();
     }
 
+    private void notifyMagnificationSystemUIConnectionChangedLocked(boolean connected) {
+        final AccessibilityUserState state = getCurrentUserStateLocked();
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            final AccessibilityServiceConnection service = state.mBoundServices.get(i);
+            service.notifyMagnificationSystemUIConnectionChangedLocked(connected);
+        }
+    }
+
     private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
             @NonNull MagnificationConfig config) {
         final AccessibilityUserState state = getCurrentUserStateLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 4cb3d24..420bac7 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -489,6 +489,14 @@
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
     @RequiresNoPermission
     @Override
+    public boolean isMagnificationSystemUIConnected() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("isMagnificationSystemUIConnected is not"
+                + " supported");
+    }
+
+    /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
+    @Override
     public boolean isMagnificationCallbackEnabled(int displayId) {
         throw new UnsupportedOperationException("isMagnificationCallbackEnabled is not supported");
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index 0719eba..7f4c808 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -126,6 +126,7 @@
 
     @ConnectionState
     private int mConnectionState = DISCONNECTED;
+    ConnectionStateChangedCallback mConnectionStateChangedCallback = null;
 
     private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100;
 
@@ -264,6 +265,9 @@
                 }
             }
         }
+        if (mConnectionStateChangedCallback != null) {
+            mConnectionStateChangedCallback.onConnectionStateChanged(connection != null);
+        }
     }
 
     /**
@@ -271,7 +275,7 @@
      */
     public boolean isConnected() {
         synchronized (mLock) {
-            return mConnectionWrapper != null;
+            return mConnectionWrapper != null && mConnectionState == CONNECTED;
         }
     }
 
@@ -1344,4 +1348,8 @@
             }
         }
     }
+
+    interface ConnectionStateChangedCallback {
+        void onConnectionStateChanged(boolean connected);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 76367a2..9b78847 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -828,6 +828,8 @@
                 mMagnificationConnectionManager = new MagnificationConnectionManager(mContext,
                         mLock, this, mAms.getTraceManager(),
                         mScaleProvider);
+                mMagnificationConnectionManager.mConnectionStateChangedCallback =
+                        mAms::notifyMagnificationSystemUIConnectionChanged;
             }
             return mMagnificationConnectionManager;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index ed8f1ab..6036839 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -147,6 +147,10 @@
         return false;
     }
 
+    public boolean isMagnificationSystemUIConnected() {
+        return mController.getMagnificationConnectionManager().isConnected();
+    }
+
     private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
             float centerX, float centerY, boolean animate, int id) {
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 215f640..4a99007 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,6 +29,7 @@
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
 import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
+import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -1478,7 +1479,13 @@
         synchronized (mVirtualDeviceLock) {
             boolean hasInterceptedIntent = false;
             for (Map.Entry<IBinder, IntentFilter> interceptor : mIntentInterceptors.entrySet()) {
-                if (interceptor.getValue().match(
+                IntentFilter intentFilter = interceptor.getValue();
+                // Explicitly match the actions because the intent filter will match any intent
+                // without an explicit action. If the intent has no action, then require that there
+                // are no actions specified in the filter either.
+                boolean explicitActionMatch = !intentInterceptionActionMatchingFix()
+                        || intent.getAction() != null || intentFilter.countActions() == 0;
+                if (explicitActionMatch && intentFilter.match(
                         intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(),
                         intent.getCategories(), TAG) >= 0) {
                     try {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 0fdf6d0..f1339e9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -232,7 +232,6 @@
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
-        "audio-permission-aidl-java",
         "cbor-java",
         "com.android.media.audio-aconfig-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 966478e..a619257 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -138,12 +138,6 @@
 
     static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
 
-    // Time needed to apply mitigation
-    private static final String MITIGATION_WINDOW_MS =
-            "persist.device_config.configuration.mitigation_window_ms";
-    @VisibleForTesting
-    static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5);
-
     // Threshold level at which or above user might experience significant disruption.
     private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
             "persist.device_config.configuration.major_user_impact_level_threshold";
@@ -216,9 +210,6 @@
     @GuardedBy("mLock")
     private boolean mSyncRequired = false;
 
-    @GuardedBy("mLock")
-    private long mLastMitigation = -1000000;
-
     @FunctionalInterface
     @VisibleForTesting
     interface SystemClock {
@@ -409,16 +400,6 @@
             Slog.w(TAG, "Could not resolve a list of failing packages");
             return;
         }
-        synchronized (mLock) {
-            final long now = mSystemClock.uptimeMillis();
-            if (Flags.recoverabilityDetection()) {
-                if (now >= mLastMitigation
-                        && (now - mLastMitigation) < getMitigationWindowMs()) {
-                    Slog.i(TAG, "Skipping onPackageFailure mitigation");
-                    return;
-                }
-            }
-        }
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
                 if (mAllObservers.isEmpty()) {
@@ -519,17 +500,10 @@
                               int currentObserverImpact,
                               int mitigationCount) {
         if (currentObserverImpact < getUserImpactLevelLimit()) {
-            synchronized (mLock) {
-                mLastMitigation = mSystemClock.uptimeMillis();
-            }
             currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount);
         }
     }
 
-    private long getMitigationWindowMs() {
-        return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
-    }
-
 
     /**
      * Called when the system server boots. If the system server is detected to be in a boot loop,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 8647750..ab34dd4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2205,12 +2205,15 @@
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
 
                     if (roForegroundAudioControl()) { // flag check
-                        final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
-                                | FOREGROUND_SERVICE_TYPE_CAMERA
-                                | FOREGROUND_SERVICE_TYPE_MICROPHONE
-                                | FOREGROUND_SERVICE_TYPE_PHONE_CALL;
-                        capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0
-                                ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0;
+                        // TODO revisit restriction of FOREGROUND_AUDIO_CONTROL when it can be
+                        //      limited to specific FGS types
+                        //final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
+                        //        | FOREGROUND_SERVICE_TYPE_CAMERA
+                        //        | FOREGROUND_SERVICE_TYPE_MICROPHONE
+                        //        | FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+                        //capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0
+                        //        ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0;
+                        capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
                     }
 
                     final boolean enabled = state.getCachedCompatChange(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8d7a1c9..8eef71e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -22,6 +22,8 @@
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -422,6 +424,10 @@
             })
     public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
             int callingUid, @Nullable String callingPackage) {
+        if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) {
+            // root and system must always opt in explicitly
+            return BackgroundStartPrivileges.ALLOW_FGS;
+        }
         boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
                 DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
                 UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 1dc1846..1d21ccb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.audio;
 
+import static android.media.audio.Flags.scoManagedByAudio;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.compat.CompatChanges;
@@ -54,6 +56,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.sysprop.BluetoothProperties;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -74,7 +77,6 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-
 /**
  * @hide
  * (non final for mocking/spying)
@@ -167,6 +169,15 @@
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2)
     public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
 
+    /** Indicates if headset profile connection and SCO audio control use the new implementation
+     * aligned with other BT profiles. True if both the feature flag Flags.scoManagedByAudio() and
+     * the system property audio.sco.managed.by.audio are true.
+     */
+    private final boolean mScoManagedByAudio;
+    /*package*/ boolean isScoManagedByAudio() {
+        return mScoManagedByAudio;
+    }
+
     //-------------------------------------------------------------------
     /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
             @NonNull AudioSystemAdapter audioSystem) {
@@ -176,7 +187,8 @@
         mDeviceInventory = new AudioDeviceInventory(this);
         mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
         mAudioSystem = audioSystem;
-
+        mScoManagedByAudio = scoManagedByAudio()
+                && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
         init();
     }
 
@@ -192,7 +204,8 @@
         mDeviceInventory = mockDeviceInventory;
         mSystemServer = mockSystemServer;
         mAudioSystem = audioSystem;
-
+        mScoManagedByAudio = scoManagedByAudio()
+                && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
         init();
     }
 
@@ -400,24 +413,24 @@
         if (client == null) {
             return;
         }
-
-        boolean isBtScoRequested = isBluetoothScoRequested();
-        if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
-            if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
-                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
-                        + uid);
-                // clean up or restore previous client selection
-                if (prevClientDevice != null) {
-                    addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
-                } else {
-                    removeCommunicationRouteClient(cb, true);
+        if (!mScoManagedByAudio) {
+            boolean isBtScoRequested = isBluetoothScoRequested();
+            if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
+                if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
+                    Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+                            + uid);
+                    // clean up or restore previous client selection
+                    if (prevClientDevice != null) {
+                        addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+                    } else {
+                        removeCommunicationRouteClient(cb, true);
+                    }
+                    postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                 }
-                postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+            } else if (!isBtScoRequested && wasBtScoRequested) {
+                mBtHelper.stopBluetoothSco(eventSource);
             }
-        } else if (!isBtScoRequested && wasBtScoRequested) {
-            mBtHelper.stopBluetoothSco(eventSource);
         }
-
         // In BT classic for communication, the device changes from a2dp to sco device, but for
         // LE Audio it stays the same and we must trigger the proper stream volume alignment, if
         // LE Audio communication device is activated after the audio system has already switched to
@@ -1685,6 +1698,8 @@
 
         pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner);
 
+        pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio);
+
         mBtHelper.dump(pw, prefix);
     }
 
@@ -1837,10 +1852,10 @@
                                             ? mAudioService.getBluetoothContextualVolumeStream()
                                             : AudioSystem.STREAM_DEFAULT);
                                 if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
-                                        || btInfo.mProfile
-                                        == BluetoothProfile.HEARING_AID) {
-                                    onUpdateCommunicationRouteClient(
-                                            isBluetoothScoRequested(),
+                                        || btInfo.mProfile == BluetoothProfile.HEARING_AID
+                                        || (mScoManagedByAudio
+                                            && btInfo.mProfile == BluetoothProfile.HEADSET)) {
+                                    onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
                                             "setBluetoothActiveDevice");
                                 }
                             }
@@ -2511,7 +2526,7 @@
             setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
                     BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
         } else {
-            if (!isBluetoothScoRequested() && wasBtScoRequested) {
+            if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) {
                 mBtHelper.stopBluetoothSco(eventSource);
             }
             updateCommunicationRoute(eventSource);
@@ -2815,4 +2830,5 @@
     void clearDeviceInventory() {
         mDeviceInventory.clearDeviceInventory();
     }
+
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0790da..287c92f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -859,6 +859,15 @@
                                 btInfo, streamType, codec, "onSetBtActiveDevice");
                     }
                     break;
+                case BluetoothProfile.HEADSET:
+                    if (mDeviceBroker.isScoManagedByAudio()) {
+                        if (switchToUnavailable) {
+                            mDeviceBroker.onSetBtScoActiveDevice(null);
+                        } else if (switchToAvailable) {
+                            mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice);
+                        }
+                    }
+                    break;
                 default: throw new IllegalArgumentException("Invalid profile "
                                  + BluetoothProfile.getProfileName(btInfo.mProfile));
             }
diff --git a/services/core/java/com/android/server/audio/AudioPolicyFacade.java b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
index 02e80d6..f652b33 100644
--- a/services/core/java/com/android/server/audio/AudioPolicyFacade.java
+++ b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
@@ -16,12 +16,14 @@
 
 package com.android.server.audio;
 
+import com.android.media.permission.INativePermissionController;
 
 /**
  * Facade to IAudioPolicyService which fulfills AudioService dependencies.
  * See @link{IAudioPolicyService.aidl}
  */
 public interface AudioPolicyFacade {
-
     public boolean isHotwordStreamSupported(boolean lookbackAudio);
+    public INativePermissionController getPermissionController();
+    public void registerOnStartTask(Runnable r);
 }
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
new file mode 100644
index 0000000..5ea3c4b
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.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.audio;
+
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.media.permission.INativePermissionController;
+import com.android.media.permission.UidPackageState;
+import com.android.server.pm.pkg.PackageState;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+/** Responsible for synchronizing system server permission state to the native audioserver. */
+public class AudioServerPermissionProvider {
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private INativePermissionController mDest;
+
+    @GuardedBy("mLock")
+    private final Map<Integer, Set<String>> mPackageMap;
+
+    /**
+     * @param appInfos - PackageState for all apps on the device, used to populate init state
+     */
+    public AudioServerPermissionProvider(Collection<PackageState> appInfos) {
+        // Initialize the package state
+        mPackageMap = generatePackageMappings(appInfos);
+    }
+
+    /**
+     * Called whenever audioserver starts (or started before us)
+     *
+     * @param pc - The permission controller interface from audioserver, which we push updates to
+     */
+    public void onServiceStart(@Nullable INativePermissionController pc) {
+        if (pc == null) return;
+        synchronized (mLock) {
+            mDest = pc;
+            resetNativePackageState();
+        }
+    }
+
+    /**
+     * Called when a package is added or removed
+     *
+     * @param uid - uid of modified package (only app-id matters)
+     * @param packageName - the (new) packageName
+     * @param isRemove - true if the package is being removed, false if it is being added
+     */
+    public void onModifyPackageState(int uid, String packageName, boolean isRemove) {
+        // No point in maintaining package mappings for uids of different users
+        uid = UserHandle.getAppId(uid);
+        synchronized (mLock) {
+            // Update state
+            Set<String> packages;
+            if (!isRemove) {
+                packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1));
+                packages.add(packageName);
+            } else {
+                packages = mPackageMap.get(uid);
+                if (packages != null) {
+                    packages.remove(packageName);
+                    if (packages.isEmpty()) mPackageMap.remove(uid);
+                }
+            }
+            // Push state to destination
+            if (mDest == null) {
+                return;
+            }
+            var state = new UidPackageState();
+            state.uid = uid;
+            state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList();
+            try {
+                mDest.updatePackagesForUid(state);
+            } catch (RemoteException e) {
+                // We will re-init the state when the service comes back up
+                mDest = null;
+            }
+        }
+    }
+
+    /** Called when full syncing package state to audioserver. */
+    @GuardedBy("mLock")
+    private void resetNativePackageState() {
+        if (mDest == null) return;
+        List<UidPackageState> states =
+                mPackageMap.entrySet().stream()
+                        .map(
+                                entry -> {
+                                    UidPackageState state = new UidPackageState();
+                                    state.uid = entry.getKey();
+                                    state.packageNames = List.copyOf(entry.getValue());
+                                    return state;
+                                })
+                        .toList();
+        try {
+            mDest.populatePackagesForUids(states);
+        } catch (RemoteException e) {
+            // We will re-init the state when the service comes back up
+            mDest = null;
+        }
+    }
+
+    /**
+     * Aggregation operation on all package states list: groups by states by app-id and merges the
+     * packageName for each state into an ArraySet.
+     */
+    private static Map<Integer, Set<String>> generatePackageMappings(
+            Collection<PackageState> appInfos) {
+        Collector<PackageState, Object, Set<String>> reducer =
+                Collectors.mapping(
+                        (PackageState p) -> p.getPackageName(),
+                        Collectors.toCollection(() -> new ArraySet(1)));
+
+        return appInfos.stream()
+                .collect(
+                        Collectors.groupingBy(
+                                /* predicate */ (PackageState p) -> p.getAppId(),
+                                /* factory */ HashMap::new,
+                                /* downstream collector */ reducer));
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cb0ad78..ef65b25 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -31,6 +31,10 @@
 import static android.Manifest.permission.QUERY_AUDIO_STATE;
 import static android.Manifest.permission.WRITE_SETTINGS;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_ARCHIVAL;
+import static android.content.Intent.EXTRA_REPLACING;
 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -48,6 +52,7 @@
 import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
 import static android.media.audio.Flags.focusFreezeTestApi;
 import static android.media.audio.Flags.roForegroundAudioControl;
+import static android.media.audio.Flags.scoManagedByAudio;
 import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
@@ -56,7 +61,9 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.media.audio.Flags.absVolumeIndexFix;
 import static com.android.media.audio.Flags.alarmMinVolumeZero;
+import static com.android.media.audio.Flags.audioserverPermissions;
 import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
 import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
 import static com.android.media.audio.Flags.setStreamVolumeOrder;
@@ -238,15 +245,18 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
 import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
 import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent;
 import com.android.server.audio.AudioServiceEvents.VolumeEvent;
+import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
 import com.android.server.pm.UserManagerService;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.utils.EventLogger;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -271,6 +281,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BooleanSupplier;
@@ -301,6 +312,8 @@
     private final SettingsAdapter mSettings;
     private final AudioPolicyFacade mAudioPolicy;
 
+    private final AudioServerPermissionProvider mPermissionProvider;
+
     private final MusicFxHelper mMusicFxHelper;
 
     /** Debug audio mode */
@@ -631,6 +644,17 @@
     // If absolute volume is supported in AVRCP device
     private volatile boolean mAvrcpAbsVolSupported = false;
 
+    private final Object mCachedAbsVolDrivingStreamsLock = new Object();
+    // Contains for all the device types which support absolute volume the current streams that
+    // are driving the volume changes
+    @GuardedBy("mCachedAbsVolDrivingStreamsLock")
+    private final HashMap<Integer, Integer> mCachedAbsVolDrivingStreams = new HashMap<>(
+            Map.of(AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.STREAM_MUSIC,
+                    AudioSystem.DEVICE_OUT_BLE_SPEAKER, AudioSystem.STREAM_MUSIC,
+                    AudioSystem.DEVICE_OUT_BLE_BROADCAST, AudioSystem.STREAM_MUSIC,
+                    AudioSystem.DEVICE_OUT_HEARING_AID, AudioSystem.STREAM_MUSIC
+            ));
+
     /**
     * Default stream type used for volume control in the absence of playback
     * e.g. user on homescreen, no app playing anything, presses hardware volume buttons, this
@@ -1008,14 +1032,22 @@
 
         public Lifecycle(Context context) {
             super(context);
+            var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor();
+            var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor);
             mService = new AudioService(context,
                               AudioSystemAdapter.getDefaultAdapter(),
                               SystemServerAdapter.getDefaultAdapter(context),
                               SettingsAdapter.getDefaultAdapter(),
                               new AudioVolumeGroupHelper(),
-                              new DefaultAudioPolicyFacade(r -> r.run()),
-                              null);
-
+                              audioPolicyFacade,
+                              null,
+                              context.getSystemService(AppOpsManager.class),
+                              PermissionEnforcer.fromContext(context),
+                              audioserverPermissions() ?
+                                initializeAudioServerPermissionProvider(
+                                    context, audioPolicyFacade, audioserverLifecycleExecutor) :
+                                    null
+                              );
         }
 
         @Override
@@ -1092,25 +1124,6 @@
     /**
      * @param context
      * @param audioSystem Adapter for {@link AudioSystem}
-     * @param systemServer Adapter for privileged functionality for system server components
-     * @param settings Adapter for {@link Settings}
-     * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup}
-     * @param audioPolicy Interface of a facade to IAudioPolicyManager
-     * @param looper Looper to use for the service's message handler. If this is null, an
-     *               {@link AudioSystemThread} is created as the messaging thread instead.
-     */
-    public AudioService(Context context, AudioSystemAdapter audioSystem,
-            SystemServerAdapter systemServer, SettingsAdapter settings,
-            AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
-            @Nullable Looper looper) {
-        this (context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
-                audioPolicy, looper, context.getSystemService(AppOpsManager.class),
-                PermissionEnforcer.fromContext(context));
-    }
-
-    /**
-     * @param context
-     * @param audioSystem Adapter for {@link AudioSystem}
      * @param systemServer Adapter for privilieged functionality for system server components
      * @param settings Adapter for {@link Settings}
      * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup}
@@ -1124,13 +1137,16 @@
     public AudioService(Context context, AudioSystemAdapter audioSystem,
             SystemServerAdapter systemServer, SettingsAdapter settings,
             AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
-            @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer) {
+            @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
+            /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
         super(enforcer);
         sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
         mContext = context;
         mContentResolver = context.getContentResolver();
         mAppOps = appOps;
 
+        mPermissionProvider = permissionProvider;
+
         mAudioSystem = audioSystem;
         mSystemServer = systemServer;
         mAudioVolumeGroupHelper = audioVolumeGroupHelper;
@@ -1471,6 +1487,13 @@
 
         // check on volume initialization
         checkVolumeRangeInitialization("AudioService()");
+
+        synchronized (mCachedAbsVolDrivingStreamsLock) {
+            mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
+                mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
+                        stream);
+            });
+        }
     }
 
     private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1503,7 +1526,9 @@
         // Register for device connection intent broadcasts.
         IntentFilter intentFilter =
                 new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
-        intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        if (!mDeviceBroker.isScoManagedByAudio()) {
+            intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        }
         intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
         if (mDisplayManager == null) {
             intentFilter.addAction(Intent.ACTION_SCREEN_ON);
@@ -1908,6 +1933,14 @@
         }
 
         onIndicateSystemReady();
+
+        synchronized (mCachedAbsVolDrivingStreamsLock) {
+            mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
+                mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
+                        stream);
+            });
+        }
+
         // indicate the end of reconfiguration phase to audio HAL
         AudioSystem.setParameters("restarting=false");
 
@@ -3734,8 +3767,10 @@
 
             int newIndex = mStreamStates[streamType].getIndex(device);
 
+            int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() :
+                    AudioSystem.STREAM_MUSIC;
             // Check if volume update should be send to AVRCP
-            if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+            if (streamTypeAlias == streamToDriveAbsVol
                     && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
                     && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                 if (DEBUG_VOL) {
@@ -4527,15 +4562,20 @@
                 + featureSpatialAudioHeadtrackingLowLatency());
         pw.println("\tandroid.media.audio.focusFreezeTestApi:"
                 + focusFreezeTestApi());
+        pw.println("\tcom.android.media.audio.audioserverPermissions:"
+                + audioserverPermissions());
         pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
                 + disablePrescaleAbsoluteVolume());
-
         pw.println("\tcom.android.media.audio.setStreamVolumeOrder:"
                 + setStreamVolumeOrder());
         pw.println("\tandroid.media.audio.roForegroundAudioControl:"
                 + roForegroundAudioControl());
+        pw.println("\tandroid.media.audio.scoManagedByAudio:"
+                + scoManagedByAudio());
         pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:"
                 + vgsVssSyncMuteOrder());
+        pw.println("\tcom.android.media.audio.absVolumeIndexFix:"
+                + absVolumeIndexFix());
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -4731,7 +4771,9 @@
             }
         }
 
-        if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+        int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() :
+                AudioSystem.STREAM_MUSIC;
+        if (streamTypeAlias == streamToDriveAbsVol
                 && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
                 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
             if (DEBUG_VOL) {
@@ -6180,6 +6222,17 @@
 
                 setLeAudioVolumeOnModeUpdate(mode, device, streamAlias, index, maxIndex);
 
+                synchronized (mCachedAbsVolDrivingStreamsLock) {
+                    mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> {
+                        int streamToDriveAbs = getBluetoothContextualVolumeStream();
+                        if (stream != streamToDriveAbs) {
+                            mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/
+                                    "", /*enabled*/true, streamToDriveAbs);
+                        }
+                        return streamToDriveAbs;
+                    });
+                }
+
                 // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
                 // connections not started by the application changing the mode when pid changes
                 mDeviceBroker.postSetModeOwner(mode, pid, uid);
@@ -7859,7 +7912,8 @@
         if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK
                 && profile != BluetoothProfile.LE_AUDIO
                 && profile != BluetoothProfile.LE_AUDIO_BROADCAST
-                && profile != BluetoothProfile.HEARING_AID) {
+                && profile != BluetoothProfile.HEARING_AID
+                && !(mDeviceBroker.isScoManagedByAudio() && profile == BluetoothProfile.HEADSET)) {
             throw new IllegalArgumentException("Illegal BluetoothProfile profile for device "
                     + previousDevice + " -> " + newDevice + ". Got: " + profile);
         }
@@ -8100,6 +8154,10 @@
             return mAudioVolumeGroup.name();
         }
 
+        public int getId() {
+            return mAudioVolumeGroup.getId();
+        }
+
         /**
          * Volume group with non null minimum index are considered as non mutable, thus
          * bijectivity is broken with potential associated stream type.
@@ -8750,24 +8808,30 @@
         }
 
         private int getAbsoluteVolumeIndex(int index) {
-            /* Special handling for Bluetooth Absolute Volume scenario
-             * If we send full audio gain, some accessories are too loud even at its lowest
-             * volume. We are not able to enumerate all such accessories, so here is the
-             * workaround from phone side.
-             * Pre-scale volume at lowest volume steps 1 2 and 3.
-             * For volume step 0, set audio gain to 0 as some accessories won't mute on their end.
-             */
-            if (index == 0) {
-                // 0% for volume 0
-                index = 0;
-            } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) {
-                // Pre-scale for volume steps 1 2 and 3
-                index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10;
+            if (absVolumeIndexFix()) {
+                // The attenuation is applied in the APM. No need to manipulate the index here
+                return index;
             } else {
-                // otherwise, full gain
-                index = (mIndexMax + 5) / 10;
+                /* Special handling for Bluetooth Absolute Volume scenario
+                 * If we send full audio gain, some accessories are too loud even at its lowest
+                 * volume. We are not able to enumerate all such accessories, so here is the
+                 * workaround from phone side.
+                 * Pre-scale volume at lowest volume steps 1 2 and 3.
+                 * For volume step 0, set audio gain to 0 as some accessories won't mute on their
+                 * end.
+                 */
+                if (index == 0) {
+                    // 0% for volume 0
+                    index = 0;
+                } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) {
+                    // Pre-scale for volume steps 1 2 and 3
+                    index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10;
+                } else {
+                    // otherwise, full gain
+                    index = (mIndexMax + 5) / 10;
+                }
+                return index;
             }
-            return index;
         }
 
         private void setStreamVolumeIndex(int index, int device) {
@@ -8778,6 +8842,11 @@
                     && !isFullyMuted()) {
                 index = 1;
             }
+
+            if (DEBUG_VOL) {
+                Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device
+                        + ")");
+            }
             mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
         }
 
@@ -8789,14 +8858,24 @@
             } else if (isAbsoluteVolumeDevice(device)
                     || isA2dpAbsoluteVolumeDevice(device)
                     || AudioSystem.isLeAudioDeviceType(device)) {
-                index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
+                // do not change the volume logic for dynamic abs behavior devices like HDMI
+                if (absVolumeIndexFix() && isAbsoluteVolumeDevice(device)) {
+                    index = getAbsoluteVolumeIndex((mIndexMax + 5) / 10);
+                } else {
+                    index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+                }
             } else if (isFullVolumeDevice(device)) {
                 index = (mIndexMax + 5)/10;
             } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                index = (mIndexMax + 5)/10;
+                if (absVolumeIndexFix()) {
+                    index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+                } else {
+                    index = (mIndexMax + 5) / 10;
+                }
             } else {
                 index = (getIndex(device) + 5)/10;
             }
+
             setStreamVolumeIndex(index, device);
         }
 
@@ -8814,11 +8893,22 @@
                                 || isA2dpAbsoluteVolumeDevice(device)
                                 || AudioSystem.isLeAudioDeviceType(device)) {
                             isAbsoluteVolume = true;
-                            index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
+                            // do not change the volume logic for dynamic abs behavior devices
+                            // like HDMI
+                            if (absVolumeIndexFix() && isAbsoluteVolumeDevice(device)) {
+                                index = getAbsoluteVolumeIndex((mIndexMax + 5) / 10);
+                            } else {
+                                index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+                            }
                         } else if (isFullVolumeDevice(device)) {
                             index = (mIndexMax + 5)/10;
                         } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                            index = (mIndexMax + 5)/10;
+                            if (absVolumeIndexFix()) {
+                                isAbsoluteVolume = true;
+                                index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+                            } else {
+                                index = (mIndexMax + 5) / 10;
+                            }
                         } else {
                             index = (mIndexMap.valueAt(i) + 5)/10;
                         }
@@ -9815,6 +9905,27 @@
 
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) {
         mAvrcpAbsVolSupported = support;
+        if (absVolumeIndexFix()) {
+            int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+            synchronized (mCachedAbsVolDrivingStreamsLock) {
+                mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
+                    if (stream != null && !mAvrcpAbsVolSupported) {
+                        mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
+                                "", /*enabled*/false, AudioSystem.DEVICE_NONE);
+                        return null;
+                    }
+                    // For A2DP and AVRCP we need to set the driving stream based on the
+                    // BT contextual stream. Hence, we need to make sure in adjustStreamVolume
+                    // and setStreamVolume that the driving abs volume stream is consistent.
+                    int streamToDriveAbs = getBluetoothContextualVolumeStream();
+                    if (stream == null || stream != streamToDriveAbs) {
+                        mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
+                                "", /*enabled*/true, streamToDriveAbs);
+                    }
+                    return streamToDriveAbs;
+                });
+            }
+        }
         sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
                     mStreamStates[AudioSystem.STREAM_MUSIC], 0);
@@ -11831,6 +11942,45 @@
     private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE
             + MediaMetrics.SEPARATOR;
 
+    private static AudioServerPermissionProvider initializeAudioServerPermissionProvider(
+            Context context, AudioPolicyFacade audioPolicy, Executor audioserverExecutor) {
+        Collection<PackageState> packageStates = null;
+        try (PackageManagerLocal.UnfilteredSnapshot snapshot =
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class)
+                        .withUnfilteredSnapshot()) {
+            packageStates = snapshot.getPackageStates().values();
+        }
+        var provider = new AudioServerPermissionProvider(packageStates);
+        audioPolicy.registerOnStartTask(() -> {
+            provider.onServiceStart(audioPolicy.getPermissionController());
+        });
+
+        // Set up event listeners
+        IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
+        packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
+        packageUpdateFilter.addDataScheme("package");
+
+        context.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+                int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+                if (intent.getBooleanExtra(EXTRA_REPLACING, false) ||
+                        intent.getBooleanExtra(EXTRA_ARCHIVAL, false)) return;
+                if (action.equals(ACTION_PACKAGE_ADDED)) {
+                    audioserverExecutor.execute(() ->
+                            provider.onModifyPackageState(uid, pkgName, false /* isRemoved */));
+                } else if (action.equals(ACTION_PACKAGE_REMOVED)) {
+                    audioserverExecutor.execute(() ->
+                            provider.onModifyPackageState(uid, pkgName, true /* isRemoved */));
+                }
+            }
+        }, packageUpdateFilter, null, null); // main thread is fine, since dispatch on executor
+        return provider;
+    }
+
     // Inform AudioFlinger of our device's low RAM attribute
     private static void readAndSetLowRamDevice()
     {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7202fa2..7f4bc74 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -598,6 +598,21 @@
     }
 
     /**
+     * Same as {@link AudioSystem#setDeviceAbsoluteVolumeEnabled(int, String, boolean, int)}
+     * @param nativeDeviceType the internal device type for which absolute volume is
+     *                         enabled/disabled
+     * @param address the address of the device for which absolute volume is enabled/disabled
+     * @param enabled whether the absolute volume is enabled/disabled
+     * @param streamToDriveAbs the stream that is controlling the absolute volume
+     * @return status of indicating the success of this operation
+     */
+    public int setDeviceAbsoluteVolumeEnabled(int nativeDeviceType, @NonNull String address,
+            boolean enabled, int streamToDriveAbs) {
+        return AudioSystem.setDeviceAbsoluteVolumeEnabled(nativeDeviceType, address, enabled,
+                streamToDriveAbs);
+    }
+
+    /**
      * Same as {@link AudioSystem#registerPolicyMixes(ArrayList, boolean)}
      * @param mixes
      * @param register
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 07daecd..6bb3eb1 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -94,14 +94,14 @@
     private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices =
             new HashMap<>();
 
-    private @Nullable BluetoothHearingAid mHearingAid;
+    private @Nullable BluetoothHearingAid mHearingAid = null;
 
-    private @Nullable BluetoothLeAudio mLeAudio;
+    private @Nullable BluetoothLeAudio mLeAudio = null;
 
     private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig;
 
     // Reference to BluetoothA2dp to query for AbsoluteVolume.
-    private @Nullable BluetoothA2dp mA2dp;
+    private @Nullable BluetoothA2dp mA2dp = null;
 
     private @Nullable BluetoothCodecConfig mA2dpCodecConfig;
 
@@ -401,50 +401,67 @@
     private void onScoAudioStateChanged(int state) {
         boolean broadcast = false;
         int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
-        switch (state) {
-            case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
-                if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
-                        && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                    mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                } else if (mDeviceBroker.isBluetoothScoRequested()) {
-                    // broadcast intent if the connection was initated by AudioService
+        if (mDeviceBroker.isScoManagedByAudio()) {
+            switch (state) {
+                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
+                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
                     broadcast = true;
-                }
-                mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
-                break;
-            case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
-                scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                // There are two cases where we want to immediately reconnect audio:
-                // 1) If a new start request was received while disconnecting: this was
-                // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
-                // 2) If audio was connected then disconnected via Bluetooth APIs and
-                // we still have pending activation requests by apps: this is indicated by
-                // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
-                if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
-                    if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
-                            && connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                            mBluetoothHeadsetDevice, mScoAudioMode)) {
-                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                        scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
+                    break;
+                case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                    mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
+                    scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                    broadcast = true;
+                    break;
+                default:
+                    break;
+            }
+        } else {
+            switch (state) {
+                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                    } else if (mDeviceBroker.isBluetoothScoRequested()) {
+                        // broadcast intent if the connection was initated by AudioService
                         broadcast = true;
-                        break;
                     }
-                }
-                if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
-                    broadcast = true;
-                }
-                mScoAudioState = SCO_STATE_INACTIVE;
-                break;
-            case BluetoothHeadset.STATE_AUDIO_CONNECTING:
-                if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
-                        && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                    mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                }
-                break;
-            default:
-                break;
+                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
+                    break;
+                case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                    mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
+                    scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                    // There are two cases where we want to immediately reconnect audio:
+                    // 1) If a new start request was received while disconnecting: this was
+                    // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
+                    // 2) If audio was connected then disconnected via Bluetooth APIs and
+                    // we still have pending activation requests by apps: this is indicated by
+                    // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
+                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+                                && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, mScoAudioMode)) {
+                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
+                            broadcast = true;
+                            break;
+                        }
+                    }
+                    if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
+                        broadcast = true;
+                    }
+                    mScoAudioState = SCO_STATE_INACTIVE;
+                    break;
+                case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                    }
+                    break;
+                default:
+                    break;
+            }
         }
         if (broadcast) {
             broadcastScoConnectionState(scoAudioState);
@@ -454,7 +471,6 @@
             newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
             sendStickyBroadcastToAll(newIntent);
         }
-
     }
     /**
      *
@@ -577,7 +593,11 @@
                 mHearingAid = null;
                 break;
             case BluetoothProfile.LE_AUDIO:
+                if (mLeAudio != null && mLeAudioCallback != null) {
+                    mLeAudio.unregisterCallback(mLeAudioCallback);
+                }
                 mLeAudio = null;
+                mLeAudioCallback = null;
                 mLeAudioCodecConfig = null;
                 break;
             case BluetoothProfile.LE_AUDIO_BROADCAST:
@@ -596,8 +616,6 @@
 
     // BluetoothLeAudio callback used to update the list of addresses in the same group as a
     // connected LE Audio device
-    MyLeAudioCallback mLeAudioCallback = null;
-
     class MyLeAudioCallback implements BluetoothLeAudio.Callback {
         @Override
         public void onCodecConfigChanged(int groupId,
@@ -620,6 +638,8 @@
         }
     }
 
+    MyLeAudioCallback mLeAudioCallback = null;
+
     // @GuardedBy("mDeviceBroker.mSetModeLock")
     @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
@@ -635,18 +655,28 @@
                 onHeadsetProfileConnected((BluetoothHeadset) proxy);
                 return;
             case BluetoothProfile.A2DP:
+                if (((BluetoothA2dp) proxy).equals(mA2dp)) {
+                    return;
+                }
                 mA2dp = (BluetoothA2dp) proxy;
                 break;
             case BluetoothProfile.HEARING_AID:
+                if (((BluetoothHearingAid) proxy).equals(mHearingAid)) {
+                    return;
+                }
                 mHearingAid = (BluetoothHearingAid) proxy;
                 break;
             case BluetoothProfile.LE_AUDIO:
-                if (mLeAudio == null) {
-                    mLeAudioCallback = new MyLeAudioCallback();
-                    ((BluetoothLeAudio) proxy).registerCallback(
-                            mContext.getMainExecutor(), mLeAudioCallback);
+                if (((BluetoothLeAudio) proxy).equals(mLeAudio)) {
+                    return;
+                }
+                if (mLeAudio != null && mLeAudioCallback != null) {
+                    mLeAudio.unregisterCallback(mLeAudioCallback);
                 }
                 mLeAudio = (BluetoothLeAudio) proxy;
+                mLeAudioCallback = new MyLeAudioCallback();
+                mLeAudio.registerCallback(
+                            mContext.getMainExecutor(), mLeAudioCallback);
                 break;
             case BluetoothProfile.A2DP_SINK:
             case BluetoothProfile.LE_AUDIO_BROADCAST:
diff --git a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
index 75febbc..09701e4 100644
--- a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
+++ b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
@@ -16,10 +16,15 @@
 
 package com.android.server.audio;
 
+import android.annotation.Nullable;
 import android.media.IAudioPolicyService;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.android.media.permission.INativePermissionController;
+
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Function;
 
@@ -43,6 +48,7 @@
                         (Function<IBinder, IAudioPolicyService>)
                                 IAudioPolicyService.Stub::asInterface,
                         e);
+        mServiceHolder.registerOnStartTask(i -> Binder.allowBlocking(i.asBinder()));
     }
 
     @Override
@@ -55,4 +61,23 @@
             throw new IllegalStateException();
         }
     }
+
+    @Override
+    public @Nullable INativePermissionController getPermissionController() {
+        IAudioPolicyService ap = mServiceHolder.checkService();
+        if (ap == null) return null;
+        try {
+            var res = Objects.requireNonNull(ap.getPermissionController());
+            Binder.allowBlocking(res.asBinder());
+            return res;
+        } catch (RemoteException e) {
+            mServiceHolder.attemptClear(ap.asBinder());
+            return null;
+        }
+    }
+
+    @Override
+    public void registerOnStartTask(Runnable task) {
+        mServiceHolder.registerOnStartTask(unused -> task.run());
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 712dcee..92fd9cb 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -14,10 +14,3 @@
   description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
   bug: "294254230"
 }
-
-flag {
-  name: "mandatory_biometrics"
-  namespace: "biometrics_framework"
-  description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
-  bug: "322081563"
-}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 182b05a..44846f3 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -168,6 +168,12 @@
             }
             SurfaceControl.DesiredDisplayModeSpecs modeSpecs =
                     mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken);
+            if (modeSpecs == null) {
+                // If mode specs is null, it most probably means that display got
+                // unplugged very rapidly.
+                Slog.w(TAG, "Desired display mode specs from SurfaceFlinger are null");
+                return;
+            }
             LocalDisplayDevice device = mDevices.get(physicalDisplayId);
             if (device == null) {
                 // Display was added.
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dbd1e65..6e027c6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1029,6 +1029,10 @@
 
     /** Helper method for sending feature discovery command */
     private void reportFeatures(boolean isTvDeviceSetting) {
+        // <Report Features> should only be sent for HDMI 2.0
+        if (getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+            return;
+        }
         // check if tv device is enabled for tv device specific RC profile setting
         if (isTvDeviceSetting) {
             if (isTvDeviceEnabled()) {
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index dd6433d..82ecb4a 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -16,12 +16,16 @@
 
 package com.android.server.inputmethod;
 
+import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
+import android.os.Process;
+import android.util.IntArray;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -29,6 +33,10 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
+import java.util.ArrayList;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
 /**
  * Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype}
  * persistent storages.
@@ -38,6 +46,152 @@
     @NonNull
     private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
 
+    record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
+                     @NonNull InputMethodMap inputMethodMap) {
+    }
+
+    static final class SingleThreadedBackgroundWriter {
+        /**
+         * A {@link ReentrantLock} used to guard {@link #mPendingTasks} and {@link #mRemovedUsers}.
+         */
+        @NonNull
+        private final ReentrantLock mLock = new ReentrantLock();
+        /**
+         * A {@link Condition} associated with {@link #mLock} for producer to unblock consumer.
+         */
+        @NonNull
+        private final Condition mLockNotifier = mLock.newCondition();
+
+        @GuardedBy("mLock")
+        @NonNull
+        private final SparseArray<WriteTask> mPendingTasks = new SparseArray<>();
+
+        @GuardedBy("mLock")
+        private final IntArray mRemovedUsers = new IntArray();
+
+        @NonNull
+        private final Thread mWriterThread = new Thread("android.ime.as") {
+
+            /**
+             * Waits until the next data has come then return the result after filtering out any
+             * already removed users.
+             *
+             * @return A list of {@link WriteTask} to be written into persistent storage
+             */
+            @WorkerThread
+            private ArrayList<WriteTask> fetchNextTasks() {
+                final SparseArray<WriteTask> tasks;
+                final IntArray removedUsers;
+                mLock.lock();
+                try {
+                    while (true) {
+                        if (mPendingTasks.size() != 0) {
+                            tasks = mPendingTasks.clone();
+                            mPendingTasks.clear();
+                            if (mRemovedUsers.size() == 0) {
+                                removedUsers = null;
+                            } else {
+                                removedUsers = mRemovedUsers.clone();
+                            }
+                            break;
+                        }
+                        mLockNotifier.awaitUninterruptibly();
+                    }
+                } finally {
+                    mLock.unlock();
+                }
+                final int size = tasks.size();
+                final ArrayList<WriteTask> result = new ArrayList<>(size);
+                for (int i = 0; i < size; ++i) {
+                    final int userId = tasks.keyAt(i);
+                    if (removedUsers != null && removedUsers.contains(userId)) {
+                        continue;
+                    }
+                    result.add(tasks.valueAt(i));
+                }
+                return result;
+            }
+
+            @WorkerThread
+            @Override
+            public void run() {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+                while (true) {
+                    final ArrayList<WriteTask> tasks = fetchNextTasks();
+                    tasks.forEach(task -> AdditionalSubtypeUtils.save(
+                            task.subtypeMap, task.inputMethodMap, task.userId));
+                }
+            }
+        };
+
+        /**
+         * Schedules a write operation
+         *
+         * @param userId the target user ID of this operation
+         * @param subtypeMap {@link AdditionalSubtypeMap} to be saved
+         * @param inputMethodMap {@link InputMethodMap} to be used to filter our {@code subtypeMap}
+         */
+        @AnyThread
+        void scheduleWriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
+                @NonNull InputMethodMap inputMethodMap) {
+            final var task = new WriteTask(userId, subtypeMap, inputMethodMap);
+            mLock.lock();
+            try {
+                if (mRemovedUsers.contains(userId)) {
+                    return;
+                }
+                mPendingTasks.put(userId, task);
+                mLockNotifier.signalAll();
+            } finally {
+                mLock.unlock();
+            }
+        }
+
+        /**
+         * Called back when a user is being created.
+         *
+         * @param userId The user ID to be created
+         */
+        @AnyThread
+        void onUserCreated(@UserIdInt int userId) {
+            mLock.lock();
+            try {
+                for (int i = mRemovedUsers.size() - 1; i >= 0; --i) {
+                    if (mRemovedUsers.get(i) == userId) {
+                        mRemovedUsers.remove(i);
+                    }
+                }
+            } finally {
+                mLock.unlock();
+            }
+        }
+
+        /**
+         * Called back when a user is being removed. Any pending task will be effectively canceled
+         * if the user is removed before the task is fulfilled.
+         *
+         * @param userId The user ID to be removed
+         */
+        @AnyThread
+        void onUserRemoved(@UserIdInt int userId) {
+            mLock.lock();
+            try {
+                mRemovedUsers.add(userId);
+                mPendingTasks.remove(userId);
+            } finally {
+                mLock.unlock();
+            }
+        }
+
+        void startThread() {
+            mWriterThread.start();
+        }
+    }
+
+    private static final SingleThreadedBackgroundWriter sWriter =
+            new SingleThreadedBackgroundWriter();
+
     /**
      * Not intended to be instantiated.
      */
@@ -64,9 +218,11 @@
             return;
         }
         sPerUserMap.put(userId, map);
-        // TODO: Offload this to a background thread.
-        // TODO: Skip if the previous data is exactly the same as new one.
-        AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
+        sWriter.scheduleWriteTask(userId, map, inputMethodMap);
+    }
+
+    static void startWriterThread() {
+        sWriter.startThread();
     }
 
     static void initialize(@NonNull Handler handler, @NonNull Context context) {
@@ -78,6 +234,7 @@
                         @Override
                         public void onUserCreated(UserInfo user, @Nullable Object token) {
                             final int userId = user.id;
+                            sWriter.onUserCreated(userId);
                             handler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     if (!sPerUserMap.contains(userId)) {
@@ -99,6 +256,7 @@
                         @Override
                         public void onUserRemoved(UserInfo user) {
                             final int userId = user.id;
+                            sWriter.onUserRemoved(userId);
                             handler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     sPerUserMap.remove(userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5843d72..7513c40 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1547,6 +1547,10 @@
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
                         newSettings.getEnabledInputMethodList());
+
+                final var unused = SystemServerInitThreadPool.submit(
+                        AdditionalSubtypeMapRepository::startWriterThread,
+                        "Start AdditionalSubtypeMapRepository's writer thread");
             }
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 1c958a9..23f947c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -367,9 +367,9 @@
         }
 
         protected void dump(final Printer pw, final String prefix) {
-            for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) {
-                final int rank = mUsageHistoryOfSubtypeListItemIndex[i];
-                final ImeSubtypeListItem item = mImeSubtypeList.get(i);
+            for (int rank = 0; rank < mUsageHistoryOfSubtypeListItemIndex.length; ++rank) {
+                final int index = mUsageHistoryOfSubtypeListItemIndex[rank];
+                final ImeSubtypeListItem item = mImeSubtypeList.get(index);
                 pw.println(prefix + "rank=" + rank + " item=" + item);
             }
         }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 563f93e..b9e0960 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -84,9 +84,16 @@
      * from the delegate selector.
      */
     private static final String LOCALES_FROM_DELEGATE_PREFS = "LocalesFromDelegatePrefs.xml";
+    private static final String LOCALES_STAGED_DATA_PREFS = "LocalesStagedDataPrefs.xml";
+    private static final String ARCHIVED_PACKAGES_PREFS = "ArchivedPackagesPrefs.xml";
     // Stage data would be deleted on reboot since it's stored in memory. So it's retained until
     // retention period OR next reboot, whichever happens earlier.
     private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
+    // Store the locales staged data for the specified package in the SharedPreferences. The format
+    // is locales s:setFromDelegate
+    // For example: en-US s:true
+    private static final String STRING_SPLIT = " s:";
+    private static final String KEY_STAGED_DATA_TIME = "staged_data_time";
 
     private final LocaleManagerService mLocaleManagerService;
     private final PackageManager mPackageManager;
@@ -94,39 +101,34 @@
     private final Context mContext;
     private final Object mStagedDataLock = new Object();
 
-    // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using
-    // SparseArray because it is more memory-efficient than a HashMap.
-    private final SparseArray<StagedData> mStagedData;
-
     // SharedPreferences to store packages whose app-locale was set by a delegate, as opposed to
     // the application setting the app-locale itself.
     private final SharedPreferences mDelegateAppLocalePackages;
+    // For unit tests
+    private final SparseArray<File> mStagedDataFiles;
+    private final File mArchivedPackagesFile;
+
     private final BroadcastReceiver mUserMonitor;
-    // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving
-    // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data
-    // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the
-    // app is installed.
-    private final Set<String> mPkgsToRestore;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManager packageManager, HandlerThread broadcastHandlerThread) {
         this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
-                new SparseArray<>(), broadcastHandlerThread, null);
+                broadcastHandlerThread, null, null, null);
     }
 
-    @VisibleForTesting LocaleManagerBackupHelper(Context context,
-            LocaleManagerService localeManagerService,
-            PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
-            HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
+    @VisibleForTesting
+    LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService,
+            PackageManager packageManager, Clock clock, HandlerThread broadcastHandlerThread,
+            SparseArray<File> stagedDataFiles, File archivedPackagesFile,
+            SharedPreferences delegateAppLocalePackages) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
         mPackageManager = packageManager;
         mClock = clock;
-        mStagedData = stagedData;
         mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
-                : createPersistedInfo();
-        mPkgsToRestore = new ArraySet<>();
-
+            : createPersistedInfo();
+        mArchivedPackagesFile = archivedPackagesFile;
+        mStagedDataFiles = stagedDataFiles;
         mUserMonitor = new UserMonitor();
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_REMOVED);
@@ -148,7 +150,7 @@
         }
 
         synchronized (mStagedDataLock) {
-            cleanStagedDataForOldEntriesLocked();
+            cleanStagedDataForOldEntriesLocked(userId);
         }
 
         HashMap<String, LocalesInfo> pkgStates = new HashMap<>();
@@ -207,14 +209,11 @@
         return out.toByteArray();
     }
 
-    private void cleanStagedDataForOldEntriesLocked() {
-        for (int i = 0; i < mStagedData.size(); i++) {
-            int userId = mStagedData.keyAt(i);
-            StagedData stagedData = mStagedData.get(userId);
-            if (stagedData.mCreationTimeMillis
-                    < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
-                deleteStagedDataLocked(userId);
-            }
+    private void cleanStagedDataForOldEntriesLocked(@UserIdInt int userId) {
+        Long created_time = getStagedDataSp(userId).getLong(KEY_STAGED_DATA_TIME, -1);
+        if (created_time != -1
+                && created_time < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
+            deleteStagedDataLocked(userId);
         }
     }
 
@@ -252,20 +251,16 @@
         // performed simultaneously.
         synchronized (mStagedDataLock) {
             // Backups for apps which are yet to be installed.
-            StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>());
-
             for (String pkgName : pkgStates.keySet()) {
                 LocalesInfo localesInfo = pkgStates.get(pkgName);
                 // Check if the application is already installed for the concerned user.
                 if (isPackageInstalledForUser(pkgName, userId)) {
-                    if (mPkgsToRestore != null) {
-                        mPkgsToRestore.remove(pkgName);
-                    }
+                    removeFromArchivedPackagesInfo(userId, pkgName);
                     // Don't apply the restore if the locales have already been set for the app.
                     checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
                 } else {
                     // Stage the data if the app isn't installed.
-                    stagedData.mPackageStates.put(pkgName, localesInfo);
+                    storeStagedDataInfo(userId, pkgName, localesInfo);
                     if (DEBUG) {
                         Slog.d(TAG, "Add locales=" + localesInfo.mLocales
                                 + " fromDelegate=" + localesInfo.mSetFromDelegate
@@ -274,8 +269,9 @@
                 }
             }
 
-            if (!stagedData.mPackageStates.isEmpty()) {
-                mStagedData.put(userId, stagedData);
+            // Create the time if the data is being staged.
+            if (!getStagedDataSp(userId).getAll().isEmpty()) {
+                storeStagedDataCreatedTime(userId);
             }
         }
     }
@@ -293,14 +289,23 @@
      * added on device.
      */
     void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
-        boolean archived = false;
+        int userId = UserHandle.getUserId(uid);
         if (extras != null) {
-            archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
-            if (archived && mPkgsToRestore != null) {
-                mPkgsToRestore.add(packageName);
+            // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon
+            // receiving the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform
+            // the data restoration during the second PACKAGE_ADDED broadcast, which is sent
+            // subsequently when the app is installed.
+            boolean archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "onPackageAddedWithExtras packageName: " + packageName + ", userId: "
+                                + userId + ", archived: " + archived);
+            }
+            if (archived) {
+                addInArchivedPackagesInfo(userId, packageName);
             }
         }
-        checkStageDataAndApplyRestore(packageName, uid);
+        checkStageDataAndApplyRestore(packageName, userId);
     }
 
     /**
@@ -310,9 +315,32 @@
      */
     void onPackageUpdateFinished(String packageName, int uid) {
         int userId = UserHandle.getUserId(uid);
-        if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) {
-            mPkgsToRestore.remove(packageName);
-            checkStageDataAndApplyRestore(packageName, uid);
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "onPackageUpdateFinished userId: " + userId + ", packageName: " + packageName);
+        }
+        String user = Integer.toString(userId);
+        File file = getArchivedPackagesFile();
+        if (file.exists()) {
+            SharedPreferences sp = getArchivedPackagesSp(file);
+            Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>()));
+            if (packageNames.remove(packageName)) {
+                SharedPreferences.Editor editor = sp.edit();
+                if (packageNames.isEmpty()) {
+                    if (!editor.remove(user).commit()) {
+                        Slog.e(TAG, "Failed to remove the user");
+                    }
+                    if (sp.getAll().isEmpty()) {
+                        file.delete();
+                    }
+                } else {
+                    // commit and log the result.
+                    if (!editor.putStringSet(user, packageNames).commit()) {
+                        Slog.e(TAG, "failed to remove the package");
+                    }
+                }
+                checkStageDataAndApplyRestore(packageName, userId);
+            }
         }
         cleanApplicationLocalesIfNeeded(packageName, userId);
     }
@@ -347,16 +375,16 @@
         }
     }
 
-    private void checkStageDataAndApplyRestore(String packageName, int uid) {
+    private void checkStageDataAndApplyRestore(String packageName, int userId) {
         try {
             synchronized (mStagedDataLock) {
-                cleanStagedDataForOldEntriesLocked();
-
-                int userId = UserHandle.getUserId(uid);
-                if (mStagedData.contains(userId)) {
-                    if (mPkgsToRestore != null) {
-                        mPkgsToRestore.remove(packageName);
+                cleanStagedDataForOldEntriesLocked(userId);
+                if (!getStagedDataSp(userId).getString(packageName, "").isEmpty()) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "checkStageDataAndApplyRestore, remove package and restore data");
                     }
+                    removeFromArchivedPackagesInfo(userId, packageName);
                     // Perform lazy restore only if the staged data exists.
                     doLazyRestoreLocked(packageName, userId);
                 }
@@ -417,8 +445,17 @@
         }
     }
 
-    private void deleteStagedDataLocked(@UserIdInt int userId) {
-        mStagedData.remove(userId);
+    void deleteStagedDataLocked(@UserIdInt int userId) {
+        File stagedFile = getStagedDataFile(userId);
+        SharedPreferences sp = getStagedDataSp(stagedFile);
+        // commit and log the result.
+        if (!sp.edit().clear().commit()) {
+            Slog.e(TAG, "Failed to commit data!");
+        }
+
+        if (stagedFile.exists()) {
+            stagedFile.delete();
+        }
     }
 
     /**
@@ -473,16 +510,6 @@
         out.endDocument();
     }
 
-    static class StagedData {
-        final long mCreationTimeMillis;
-        final HashMap<String, LocalesInfo> mPackageStates;
-
-        StagedData(long creationTimeMillis, HashMap<String, LocalesInfo> pkgStates) {
-            mCreationTimeMillis = creationTimeMillis;
-            mPackageStates = pkgStates;
-        }
-    }
-
     static class LocalesInfo {
         final String mLocales;
         final boolean mSetFromDelegate;
@@ -508,6 +535,7 @@
                     synchronized (mStagedDataLock) {
                         deleteStagedDataLocked(userId);
                         removeProfileFromPersistedInfo(userId);
+                        removeArchivedPackagesForUser(userId);
                     }
                 }
             } catch (Exception e) {
@@ -533,26 +561,159 @@
             return;
         }
 
-        StagedData stagedData = mStagedData.get(userId);
-        for (String pkgName : stagedData.mPackageStates.keySet()) {
-            LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName);
-
-            if (pkgName.equals(packageName)) {
-
-                checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
-
-                // Remove the restored entry from the staged data list.
-                stagedData.mPackageStates.remove(pkgName);
-
-                // Remove the stage data entry for user if there are no more packages to restore.
-                if (stagedData.mPackageStates.isEmpty()) {
-                    mStagedData.remove(userId);
-                }
-
-                // No need to loop further after restoring locales because the staged data will
-                // contain at most one entry for the newly added package.
-                break;
+        SharedPreferences sp = getStagedDataSp(userId);
+        String value = sp.getString(packageName, "");
+        if (!value.isEmpty()) {
+            String[] info = value.split(STRING_SPLIT);
+            if (info == null || info.length != 2) {
+                Slog.e(TAG, "Failed to restore data");
+                return;
             }
+            LocalesInfo localesInfo = new LocalesInfo(info[0], Boolean.parseBoolean(info[1]));
+            checkExistingLocalesAndApplyRestore(packageName, localesInfo, userId);
+
+            // Remove the restored entry from the staged data list.
+            if (!sp.edit().remove(packageName).commit()) {
+                Slog.e(TAG, "Failed to commit data!");
+            }
+        }
+
+        // Remove the stage data entry for user if there are no more packages to restore.
+        if (sp.getAll().size() == 1 && sp.getLong(KEY_STAGED_DATA_TIME, -1) != -1) {
+            deleteStagedDataLocked(userId);
+        }
+    }
+
+    private File getStagedDataFile(@UserIdInt int userId) {
+        return mStagedDataFiles == null ? new File(Environment.getDataSystemDeDirectory(userId),
+            LOCALES_STAGED_DATA_PREFS) : mStagedDataFiles.get(userId);
+    }
+
+    private SharedPreferences getStagedDataSp(File file) {
+        return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext()
+            .getSharedPreferences(file, Context.MODE_PRIVATE)
+            : mContext.getSharedPreferences(file, Context.MODE_PRIVATE);
+    }
+
+    private SharedPreferences getStagedDataSp(@UserIdInt int userId) {
+        return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext()
+            .getSharedPreferences(getStagedDataFile(userId), Context.MODE_PRIVATE)
+            : mContext.getSharedPreferences(mStagedDataFiles.get(userId), Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Store the staged locales info.
+     */
+    private void storeStagedDataInfo(@UserIdInt int userId, @NonNull String packageName,
+            @NonNull LocalesInfo localesInfo) {
+        if (DEBUG) {
+            Slog.d(TAG, "storeStagedDataInfo, userId: " + userId + ", packageName: " + packageName
+                    + ", localesInfo.mLocales: " + localesInfo.mLocales
+                    + ", localesInfo.mSetFromDelegate: " + localesInfo.mSetFromDelegate);
+        }
+        String info =
+                localesInfo.mLocales + STRING_SPLIT + String.valueOf(localesInfo.mSetFromDelegate);
+        SharedPreferences sp = getStagedDataSp(userId);
+        // commit and log the result.
+        if (!sp.edit().putString(packageName, info).commit()) {
+            Slog.e(TAG, "Failed to commit data!");
+        }
+    }
+
+    /**
+     * Store the time of creation for staged locales info.
+     */
+    private void storeStagedDataCreatedTime(@UserIdInt int userId) {
+        SharedPreferences sp = getStagedDataSp(userId);
+        // commit and log the result.
+        if (!sp.edit().putLong(KEY_STAGED_DATA_TIME, mClock.millis()).commit()) {
+            Slog.e(TAG, "Failed to commit data!");
+        }
+    }
+
+    private File getArchivedPackagesFile() {
+        return mArchivedPackagesFile == null ? new File(
+            Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
+            ARCHIVED_PACKAGES_PREFS) : mArchivedPackagesFile;
+    }
+
+    private SharedPreferences getArchivedPackagesSp(File file) {
+        return mArchivedPackagesFile == null ? mContext.createDeviceProtectedStorageContext()
+            .getSharedPreferences(file, Context.MODE_PRIVATE)
+            : mContext.getSharedPreferences(file, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Add the package into the archived packages list.
+     */
+    private void addInArchivedPackagesInfo(@UserIdInt int userId, @NonNull String packageName) {
+        String user = Integer.toString(userId);
+        SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile());
+        Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>()));
+        if (DEBUG) {
+            Slog.d(TAG, "addInArchivedPackagesInfo before packageNames: " + packageNames
+                    + ", packageName: " + packageName);
+        }
+        if (packageNames.add(packageName)) {
+            // commit and log the result.
+            if (!sp.edit().putStringSet(user, packageNames).commit()) {
+                Slog.e(TAG, "failed to add the package");
+            }
+        }
+    }
+
+    /**
+     * Remove the package from the archived packages list.
+     */
+    private void removeFromArchivedPackagesInfo(@UserIdInt int userId,
+            @NonNull String packageName) {
+        File file = getArchivedPackagesFile();
+        if (file.exists()) {
+            String user = Integer.toString(userId);
+            SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile());
+            Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>()));
+            if (DEBUG) {
+                Slog.d(TAG, "removeFromArchivedPackagesInfo before packageNames: " + packageNames
+                        + ", packageName: " + packageName);
+            }
+            if (packageNames.remove(packageName)) {
+                SharedPreferences.Editor editor = sp.edit();
+                if (packageNames.isEmpty()) {
+                    if (!editor.remove(user).commit()) {
+                        Slog.e(TAG, "Failed to remove user");
+                    }
+                    if (sp.getAll().isEmpty()) {
+                        file.delete();
+                    }
+                } else {
+                    // commit and log the result.
+                    if (!editor.putStringSet(user, packageNames).commit()) {
+                        Slog.e(TAG, "failed to remove the package");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove the user from the archived packages list.
+     */
+    private void removeArchivedPackagesForUser(@UserIdInt int userId) {
+        String user = Integer.toString(userId);
+        File file = getArchivedPackagesFile();
+        SharedPreferences sp = getArchivedPackagesSp(file);
+
+        if (sp == null || !sp.contains(user)) {
+            Slog.w(TAG, "The profile is not existed in the archived package info");
+            return;
+        }
+
+        if (!sp.edit().remove(user).commit()) {
+            Slog.e(TAG, "Failed to remove user");
+        }
+
+        if (sp.getAll().isEmpty() && file.exists()) {
+            file.delete();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 363684f..09605fe 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -59,7 +59,7 @@
     public abstract void requestCreateSession(
             long requestId,
             String packageName,
-            String routeId,
+            String routeOriginalId,
             @Nullable Bundle sessionHints,
             @RoutingSessionInfo.TransferReason int transferReason,
             @NonNull UserHandle transferInitiatorUserHandle,
@@ -77,13 +77,15 @@
             long requestId,
             @NonNull UserHandle transferInitiatorUserHandle,
             @NonNull String transferInitiatorPackageName,
-            String sessionId,
-            String routeId,
+            String sessionOriginalId,
+            String routeOriginalId,
             @RoutingSessionInfo.TransferReason int transferReason);
 
-    public abstract void setRouteVolume(long requestId, String routeId, int volume);
-    public abstract void setSessionVolume(long requestId, String sessionId, int volume);
-    public abstract void prepareReleaseSession(@NonNull String sessionId);
+    public abstract void setRouteVolume(long requestId, String routeOriginalId, int volume);
+
+    public abstract void setSessionVolume(long requestId, String sessionOriginalId, int volume);
+
+    public abstract void prepareReleaseSession(@NonNull String sessionUniqueId);
 
     @NonNull
     public String getUniqueId() {
@@ -197,8 +199,8 @@
          */
         public final long mRequestId;
 
-        /** The {@link MediaRoute2Info#getId() id} of the target route. */
-        @NonNull public final String mTargetRouteId;
+        /** The {@link MediaRoute2Info#getOriginalId()} original id} of the target route. */
+        @NonNull public final String mTargetOriginalRouteId;
 
         @RoutingSessionInfo.TransferReason public final int mTransferReason;
 
@@ -209,23 +211,23 @@
 
         SessionCreationOrTransferRequest(
                 long requestId,
-                @NonNull String routeId,
+                @NonNull String targetOriginalRouteId,
                 @RoutingSessionInfo.TransferReason int transferReason,
                 @NonNull UserHandle transferInitiatorUserHandle,
                 @NonNull String transferInitiatorPackageName) {
             mRequestId = requestId;
-            mTargetRouteId = routeId;
+            mTargetOriginalRouteId = targetOriginalRouteId;
             mTransferReason = transferReason;
             mTransferInitiatorUserHandle = transferInitiatorUserHandle;
             mTransferInitiatorPackageName = transferInitiatorPackageName;
         }
 
         public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) {
-            return route2Info != null && mTargetRouteId.equals(route2Info.getId());
+            return route2Info != null && mTargetOriginalRouteId.equals(route2Info.getOriginalId());
         }
 
-        public boolean isTargetRouteIdInList(@NonNull List<String> routesList) {
-            return routesList.stream().anyMatch(mTargetRouteId::equals);
+        public boolean isTargetRouteIdInList(@NonNull List<String> routeOriginalIdList) {
+            return routeOriginalIdList.stream().anyMatch(mTargetOriginalRouteId::equals);
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 386657e..71cbcb9 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -103,13 +103,14 @@
     public void requestCreateSession(
             long requestId,
             String packageName,
-            String routeId,
+            String routeOriginalId,
             Bundle sessionHints,
             @RoutingSessionInfo.TransferReason int transferReason,
             @NonNull UserHandle transferInitiatorUserHandle,
             @NonNull String transferInitiatorPackageName) {
         if (mConnectionReady) {
-            mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
+            mActiveConnection.requestCreateSession(
+                    requestId, packageName, routeOriginalId, sessionHints);
             updateBinding();
         }
     }
@@ -153,35 +154,35 @@
             long requestId,
             @NonNull UserHandle transferInitiatorUserHandle,
             @NonNull String transferInitiatorPackageName,
-            String sessionId,
-            String routeId,
+            String sessionOriginalId,
+            String routeOriginalId,
             @RoutingSessionInfo.TransferReason int transferReason) {
         if (mConnectionReady) {
-            mActiveConnection.transferToRoute(requestId, sessionId, routeId);
+            mActiveConnection.transferToRoute(requestId, sessionOriginalId, routeOriginalId);
         }
     }
 
     @Override
-    public void setRouteVolume(long requestId, String routeId, int volume) {
+    public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
         if (mConnectionReady) {
-            mActiveConnection.setRouteVolume(requestId, routeId, volume);
+            mActiveConnection.setRouteVolume(requestId, routeOriginalId, volume);
             updateBinding();
         }
     }
 
     @Override
-    public void setSessionVolume(long requestId, String sessionId, int volume) {
+    public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
         if (mConnectionReady) {
-            mActiveConnection.setSessionVolume(requestId, sessionId, volume);
+            mActiveConnection.setSessionVolume(requestId, sessionOriginalId, volume);
             updateBinding();
         }
     }
 
     @Override
-    public void prepareReleaseSession(@NonNull String sessionId) {
+    public void prepareReleaseSession(@NonNull String sessionUniqueId) {
         synchronized (mLock) {
             for (RoutingSessionInfo session : mSessionInfos) {
-                if (TextUtils.equals(session.getId(), sessionId)) {
+                if (TextUtils.equals(session.getId(), sessionUniqueId)) {
                     mSessionInfos.remove(session);
                     mReleasingSessions.add(session);
                     break;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 76930a0..6b409ee 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -158,20 +158,20 @@
     public void requestCreateSession(
             long requestId,
             String packageName,
-            String routeId,
+            String routeOriginalId,
             Bundle sessionHints,
             @RoutingSessionInfo.TransferReason int transferReason,
             @NonNull UserHandle transferInitiatorUserHandle,
             @NonNull String transferInitiatorPackageName) {
         // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
         // a route ID different from the default route ID. The service should've filtered.
-        if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
+        if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
             return;
         }
 
         if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
-            if (TextUtils.equals(routeId, mSelectedRouteId)) {
+            if (TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
                 RoutingSessionInfo currentSessionInfo;
                 synchronized (mLock) {
                     currentSessionInfo = mSessionInfos.get(0);
@@ -192,7 +192,7 @@
             mPendingSessionCreationOrTransferRequest =
                     new SessionCreationOrTransferRequest(
                             requestId,
-                            routeId,
+                            routeOriginalId,
                             RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
                             transferInitiatorUserHandle,
                             transferInitiatorPackageName);
@@ -204,7 +204,7 @@
                 transferInitiatorUserHandle,
                 transferInitiatorPackageName,
                 SYSTEM_SESSION_ID,
-                routeId,
+                routeOriginalId,
                 transferReason);
     }
 
@@ -234,15 +234,15 @@
             long requestId,
             @NonNull UserHandle transferInitiatorUserHandle,
             @NonNull String transferInitiatorPackageName,
-            String sessionId,
-            String routeId,
+            String sessionOriginalId,
+            String routeOriginalId,
             @RoutingSessionInfo.TransferReason int transferReason) {
         String selectedDeviceRouteId = mDeviceRouteController.getSelectedRoute().getId();
-        if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
+        if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
                 // Transfer to the default route (which is the selected route). We replace the id to
                 // be the selected route id so that the transfer reason gets updated.
-                routeId = selectedDeviceRouteId;
+                routeOriginalId = selectedDeviceRouteId;
             } else {
                 Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
                 return;
@@ -254,18 +254,18 @@
                 mPendingTransferRequest =
                         new SessionCreationOrTransferRequest(
                                 requestId,
-                                routeId,
+                                routeOriginalId,
                                 transferReason,
                                 transferInitiatorUserHandle,
                                 transferInitiatorPackageName);
             }
         }
 
-        String finalRouteId = routeId; // Make a final copy to use it in the lambda.
+        String finalRouteId = routeOriginalId; // Make a final copy to use it in the lambda.
         boolean isAvailableDeviceRoute =
                 mDeviceRouteController.getAvailableRoutes().stream()
                         .anyMatch(it -> it.getId().equals(finalRouteId));
-        boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRouteId);
+        boolean isSelectedDeviceRoute = TextUtils.equals(routeOriginalId, selectedDeviceRouteId);
 
         if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
             // The requested route is managed by the device route controller. Note that the selected
@@ -273,12 +273,12 @@
             // of the routing session). If the selected device route is transferred to, we need to
             // make the bluetooth routes inactive so that the device route becomes the selected
             // route of the routing session.
-            mDeviceRouteController.transferTo(routeId);
+            mDeviceRouteController.transferTo(routeOriginalId);
             mBluetoothRouteController.transferTo(null);
         } else {
             // The requested route is managed by the bluetooth route controller.
             mDeviceRouteController.transferTo(null);
-            mBluetoothRouteController.transferTo(routeId);
+            mBluetoothRouteController.transferTo(routeOriginalId);
         }
 
         if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
@@ -288,20 +288,20 @@
     }
 
     @Override
-    public void setRouteVolume(long requestId, String routeId, int volume) {
-        if (!TextUtils.equals(routeId, mSelectedRouteId)) {
+    public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
+        if (!TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
             return;
         }
         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
     }
 
     @Override
-    public void setSessionVolume(long requestId, String sessionId, int volume) {
+    public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
         // Do nothing since we don't support grouping volume yet.
     }
 
     @Override
-    public void prepareReleaseSession(String sessionId) {
+    public void prepareReleaseSession(String sessionUniqueId) {
         // Do nothing since the system session persists.
     }
 
@@ -503,12 +503,13 @@
         }
 
         long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId;
-        if (mPendingSessionCreationOrTransferRequest.mTargetRouteId.equals(mSelectedRouteId)) {
+        if (mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId.equals(
+                mSelectedRouteId)) {
             if (DEBUG) {
                 Slog.w(
                         TAG,
                         "Session creation success to route "
-                                + mPendingSessionCreationOrTransferRequest.mTargetRouteId);
+                                + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId);
             }
             mPendingSessionCreationOrTransferRequest = null;
             mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo);
@@ -520,7 +521,8 @@
                     Slog.w(
                             TAG,
                             "Session creation failed to route "
-                                    + mPendingSessionCreationOrTransferRequest.mTargetRouteId);
+                                    + mPendingSessionCreationOrTransferRequest
+                                            .mTargetOriginalRouteId);
                 }
                 mPendingSessionCreationOrTransferRequest = null;
                 mCallback.onRequestFailed(
@@ -529,7 +531,7 @@
                 Slog.w(
                         TAG,
                         "Session creation waiting state to route "
-                                + mPendingSessionCreationOrTransferRequest.mTargetRouteId);
+                                + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId);
             }
         }
     }
@@ -541,7 +543,8 @@
         // See b/307723189 for context
         for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) {
             if (TextUtils.equals(
-                    btRoute.getId(), mPendingSessionCreationOrTransferRequest.mTargetRouteId)) {
+                    btRoute.getId(),
+                    mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index dec97fb..0d1095f 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -704,7 +704,8 @@
             return false;
         }
 
-        if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) {
+        if (isAppOptedOutOfArchiving(packageName,
+                    UserHandle.getUid(userId, ps.getAppId()))) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e00e813..f4b61e7 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -72,7 +72,6 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.Xml;
@@ -88,6 +87,7 @@
 import com.android.server.SystemService;
 import com.android.server.servicewatcher.CurrentUserServiceSupplier;
 import com.android.server.servicewatcher.ServiceWatcher;
+import com.android.server.utils.Slogf;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -98,7 +98,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-
+import java.util.Objects;
 
 /**
  * Manages trust agents and trust listeners.
@@ -362,6 +362,13 @@
     }
 
     private void scheduleTrustTimeout(boolean override, boolean isTrustableTimeout) {
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "scheduleTrustTimeout(override=%s, isTrustable=%s)",
+                    override,
+                    isTrustableTimeout);
+        }
         int shouldOverride = override ? 1 : 0;
         int trustableTimeout = isTrustableTimeout ? 1 : 0;
         mHandler.obtainMessage(MSG_SCHEDULE_TRUST_TIMEOUT, shouldOverride,
@@ -370,6 +377,13 @@
 
     private void handleScheduleTrustTimeout(boolean shouldOverride, TimeoutType timeoutType) {
         int userId = mCurrentUser;
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "handleScheduleTrustTimeout(shouldOverride=%s, timeoutType=%s)",
+                    shouldOverride,
+                    timeoutType);
+        }
         if (timeoutType == TimeoutType.TRUSTABLE) {
             // don't override the hard timeout unless biometric or knowledge factor authentication
             // occurs which isn't where this is called from. Override the idle timeout what the
@@ -383,6 +397,7 @@
 
     /* Override both the idle and hard trustable timeouts */
     private void refreshTrustableTimers(int userId) {
+        if (DEBUG) Slogf.d(TAG, "refreshTrustableTimers(userId=%s)", userId);
         handleScheduleTrustableTimeouts(userId, true /* overrideIdleTimeout */,
                 true /* overrideHardTimeout */);
     }
@@ -405,13 +420,20 @@
     }
 
     private void handleScheduleTrustedTimeout(int userId, boolean shouldOverride) {
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "handleScheduleTrustedTimeout(userId=%s, shouldOverride=%s)",
+                    userId,
+                    shouldOverride);
+        }
         long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS;
         TrustedTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
 
         // Cancel existing trust timeouts for this user if needed.
         if (alarm != null) {
             if (!shouldOverride && alarm.isQueued()) {
-                if (DEBUG) Slog.d(TAG, "Found existing trust timeout alarm. Skipping.");
+                if (DEBUG) Slogf.d(TAG, "Found existing trust timeout alarm. Skipping.");
                 return;
             }
             mAlarmManager.cancel(alarm);
@@ -420,7 +442,9 @@
             mTrustTimeoutAlarmListenerForUser.put(userId, alarm);
         }
 
-        if (DEBUG) Slog.d(TAG, "\tSetting up trust timeout alarm");
+        if (DEBUG) {
+            Slogf.d(TAG, "\tSetting up trust timeout alarm triggering at elapsedRealTime=%s", when);
+        }
         alarm.setQueued(true /* isQueued */);
         mAlarmManager.setExact(
                 AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
@@ -434,6 +458,13 @@
     }
 
     private void setUpIdleTimeout(int userId, boolean overrideIdleTimeout) {
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "setUpIdleTimeout(userId=%s, overrideIdleTimeout=%s)",
+                    userId,
+                    overrideIdleTimeout);
+        }
         long when = SystemClock.elapsedRealtime() + TRUSTABLE_IDLE_TIMEOUT_IN_MILLIS;
         TrustableTimeoutAlarmListener alarm = mIdleTrustableTimeoutAlarmListenerForUser.get(userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.SCHEDULE_EXACT_ALARM, null);
@@ -441,7 +472,7 @@
         // Cancel existing trustable timeouts for this user if needed.
         if (alarm != null) {
             if (!overrideIdleTimeout && alarm.isQueued()) {
-                if (DEBUG) Slog.d(TAG, "Found existing trustable timeout alarm. Skipping.");
+                if (DEBUG) Slogf.d(TAG, "Found existing trustable timeout alarm. Skipping.");
                 return;
             }
             mAlarmManager.cancel(alarm);
@@ -450,7 +481,12 @@
             mIdleTrustableTimeoutAlarmListenerForUser.put(userId, alarm);
         }
 
-        if (DEBUG) Slog.d(TAG, "\tSetting up trustable idle timeout alarm");
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "\tSetting up trustable idle timeout alarm triggering at elapsedRealTime=%s",
+                    when);
+        }
         alarm.setQueued(true /* isQueued */);
         mAlarmManager.setExact(
                 AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
@@ -458,6 +494,13 @@
     }
 
     private void setUpHardTimeout(int userId, boolean overrideHardTimeout) {
+        if (DEBUG) {
+            Slogf.i(
+                    TAG,
+                    "setUpHardTimeout(userId=%s, overrideHardTimeout=%s)",
+                    userId,
+                    overrideHardTimeout);
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.SCHEDULE_EXACT_ALARM, null);
         TrustableTimeoutAlarmListener alarm = mTrustableTimeoutAlarmListenerForUser.get(userId);
 
@@ -472,7 +515,13 @@
             } else if (overrideHardTimeout) {
                 mAlarmManager.cancel(alarm);
             }
-            if (DEBUG) Slog.d(TAG, "\tSetting up trustable hard timeout alarm");
+            if (DEBUG) {
+                Slogf.d(
+                        TAG,
+                        "\tSetting up trustable hard timeout alarm triggering at "
+                                + "elapsedRealTime=%s",
+                        when);
+            }
             alarm.setQueued(true /* isQueued */);
             mAlarmManager.setExact(
                     AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
@@ -503,6 +552,12 @@
         public int hashCode() {
             return component.hashCode() * 31 + userId;
         }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "AgentInfo{label=%s, component=%s, userId=%s}", label, component, userId);
+        }
     }
 
     private void updateTrustAll() {
@@ -532,6 +587,15 @@
             int flags,
             boolean isFromUnlock,
             @Nullable AndroidFuture<GrantTrustResult> resultCallback) {
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "updateTrust(userId=%s, flags=%s, isFromUnlock=%s, resultCallbackPresent=%s)",
+                    userId,
+                    flags,
+                    isFromUnlock,
+                    Objects.isNull(resultCallback));
+        }
         boolean managed = aggregateIsTrustManaged(userId);
         dispatchOnTrustManagedChanged(managed, userId);
         if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -559,27 +623,50 @@
                     (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
             boolean canMoveToTrusted =
                     alreadyUnlocked || isFromUnlock || renewingTrust || isAutomotive();
-            boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
+            boolean updatingTrustForCurrentUser = (userId == mCurrentUser);
+
+            if (DEBUG) {
+                Slogf.d(
+                        TAG,
+                        "updateTrust: alreadyUnlocked=%s, wasTrusted=%s, wasTrustable=%s, "
+                                + "renewingTrust=%s, canMoveToTrusted=%s, "
+                                + "updatingTrustForCurrentUser=%s",
+                        alreadyUnlocked,
+                        wasTrusted,
+                        wasTrustable,
+                        renewingTrust,
+                        canMoveToTrusted,
+                        updatingTrustForCurrentUser);
+            }
 
             if (trustedByAtLeastOneAgent && wasTrusted) {
                 // no change
                 return;
-            } else if (trustedByAtLeastOneAgent && canMoveToTrusted
-                    && upgradingTrustForCurrentUser) {
+            } else if (trustedByAtLeastOneAgent
+                    && canMoveToTrusted
+                    && updatingTrustForCurrentUser) {
                 pendingTrustState = TrustState.TRUSTED;
-            } else if (trustableByAtLeastOneAgent && (wasTrusted || wasTrustable)
-                    && upgradingTrustForCurrentUser) {
+            } else if (trustableByAtLeastOneAgent
+                    && (wasTrusted || wasTrustable)
+                    && updatingTrustForCurrentUser) {
                 pendingTrustState = TrustState.TRUSTABLE;
             } else {
                 pendingTrustState = TrustState.UNTRUSTED;
             }
+            if (DEBUG) Slogf.d(TAG, "updateTrust: pendingTrustState=%s", pendingTrustState);
 
             mUserTrustState.put(userId, pendingTrustState);
         }
-        if (DEBUG) Slog.d(TAG, "pendingTrustState: " + pendingTrustState);
 
         boolean isNowTrusted = pendingTrustState == TrustState.TRUSTED;
         boolean newlyUnlocked = !alreadyUnlocked && isNowTrusted;
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "updateTrust: isNowTrusted=%s, newlyUnlocked=%s",
+                    isNowTrusted,
+                    newlyUnlocked);
+        }
         maybeActiveUnlockRunningChanged(userId);
         dispatchOnTrustChanged(
                 isNowTrusted, newlyUnlocked, userId, flags, getTrustGrantedMessages(userId));
@@ -598,13 +685,13 @@
         boolean shouldSendCallback = newlyUnlocked;
         if (shouldSendCallback) {
             if (resultCallback != null) {
-                if (DEBUG) Slog.d(TAG, "calling back with UNLOCKED_BY_GRANT");
+                if (DEBUG) Slogf.d(TAG, "calling back with UNLOCKED_BY_GRANT");
                 resultCallback.complete(new GrantTrustResult(STATUS_UNLOCKED_BY_GRANT));
             }
         }
 
         if ((wasTrusted || wasTrustable) && pendingTrustState == TrustState.UNTRUSTED) {
-            if (DEBUG) Slog.d(TAG, "Trust was revoked, destroy trustable alarms");
+            if (DEBUG) Slogf.d(TAG, "Trust was revoked, destroy trustable alarms");
             cancelBothTrustableAlarms(userId);
         }
     }
@@ -650,7 +737,7 @@
         try {
             WindowManagerGlobal.getWindowManagerService().lockNow(null);
         } catch (RemoteException e) {
-            Slog.e(TAG, "Error locking screen when called from trust agent");
+            Slogf.e(TAG, "Error locking screen when called from trust agent");
         }
     }
 
@@ -659,8 +746,9 @@
     }
 
     void refreshAgentList(int userIdOrAll) {
-        if (DEBUG) Slog.d(TAG, "refreshAgentList(" + userIdOrAll + ")");
+        if (DEBUG) Slogf.d(TAG, "refreshAgentList(userIdOrAll=%s)", userIdOrAll);
         if (!mTrustAgentsCanRun) {
+            if (DEBUG) Slogf.d(TAG, "Did not refresh agent list because agents cannot run.");
             return;
         }
         if (userIdOrAll != UserHandle.USER_ALL && userIdOrAll < UserHandle.USER_SYSTEM) {
@@ -686,18 +774,30 @@
             if (userInfo == null || userInfo.partial || !userInfo.isEnabled()
                     || userInfo.guestToRemove) continue;
             if (!userInfo.supportsSwitchToByUser()) {
-                if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
-                        + ": switchToByUser=false");
+                if (DEBUG) {
+                    Slogf.d(
+                            TAG,
+                            "refreshAgentList: skipping user %s: switchToByUser=false",
+                            userInfo.id);
+                }
                 continue;
             }
             if (!mActivityManager.isUserRunning(userInfo.id)) {
-                if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
-                        + ": user not started");
+                if (DEBUG) {
+                    Slogf.d(
+                            TAG,
+                            "refreshAgentList: skipping user %s: user not started",
+                            userInfo.id);
+                }
                 continue;
             }
             if (!lockPatternUtils.isSecure(userInfo.id)) {
-                if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
-                        + ": no secure credential");
+                if (DEBUG) {
+                    Slogf.d(
+                            TAG,
+                            "refreshAgentList: skipping user %s: no secure credential",
+                            userInfo.id);
+                }
                 continue;
             }
 
@@ -708,8 +808,12 @@
 
             List<ComponentName> enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id);
             if (enabledAgents.isEmpty()) {
-                if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
-                        + ": no agents enabled by user");
+                if (DEBUG) {
+                    Slogf.d(
+                            TAG,
+                            "refreshAgentList: skipping user %s: no agents enabled by user",
+                            userInfo.id);
+                }
                 continue;
             }
             List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userInfo.id);
@@ -717,9 +821,13 @@
                 ComponentName name = getComponentName(resolveInfo);
 
                 if (!enabledAgents.contains(name)) {
-                    if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping "
-                            + name.flattenToShortString() + " u"+ userInfo.id
-                            + ": not enabled by user");
+                    if (DEBUG) {
+                        Slogf.d(
+                                TAG,
+                                "refreshAgentList: skipping %s u%s: not enabled by user",
+                                name.flattenToShortString(),
+                                userInfo.id);
+                    }
                     continue;
                 }
                 if (disableTrustAgents) {
@@ -727,9 +835,13 @@
                             dpm.getTrustAgentConfiguration(null /* admin */, name, userInfo.id);
                     // Disable agent if no features are enabled.
                     if (config == null || config.isEmpty()) {
-                        if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping "
-                                + name.flattenToShortString() + " u"+ userInfo.id
-                                + ": not allowed by DPM");
+                        if (DEBUG) {
+                            Slogf.d(
+                                    TAG,
+                                    "refreshAgentList: skipping %s u%s: not allowed by DPM",
+                                    name.flattenToShortString(),
+                                    userInfo.id);
+                        }
                         continue;
                     }
                 }
@@ -752,15 +864,26 @@
                 }
 
                 if (directUnlock) {
-                    if (DEBUG) Slog.d(TAG, "refreshAgentList: trustagent " + name
-                            + "of user " + userInfo.id + "can unlock user profile.");
+                    if (DEBUG) {
+                        Slogf.d(
+                                TAG,
+                                "refreshAgentList: trustagent %s of user %s can unlock user "
+                                        + "profile.",
+                                name,
+                                userInfo.id);
+                    }
                 }
 
                 if (!mUserManager.isUserUnlockingOrUnlocked(userInfo.id)
                         && !directUnlock) {
-                    if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
-                            + "'s trust agent " + name + ": FBE still locked and "
-                            + " the agent cannot unlock user profile.");
+                    if (DEBUG) {
+                        Slogf.d(
+                                TAG,
+                                "refreshAgentList: skipping user %s's trust agent %s: FBE still "
+                                        + "locked and the agent cannot unlock user profile.",
+                                userInfo.id,
+                                name);
+                    }
                     continue;
                 }
 
@@ -769,11 +892,16 @@
                     if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) {
                         if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
                             || !directUnlock) {
-                            if (DEBUG)
-                                Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
-                                    + ": prevented by StrongAuthTracker = 0x"
-                                    + Integer.toHexString(mStrongAuthTracker.getStrongAuthForUser(
-                                    userInfo.id)));
+                            if (DEBUG) {
+                                Slogf.d(
+                                        TAG,
+                                        "refreshAgentList: skipping user %s: prevented by "
+                                                + "StrongAuthTracker = 0x%s",
+                                        userInfo.id,
+                                        Integer.toHexString(
+                                                mStrongAuthTracker.getStrongAuthForUser(
+                                                        userInfo.id)));
+                            }
                             continue;
                         }
                     }
@@ -804,6 +932,15 @@
             }
         }
 
+        if (DEBUG) {
+            Slogf.d(
+                    TAG,
+                    "refreshAgentList: userInfos=%s, obsoleteAgents=%s, trustMayHaveChanged=%s",
+                    userInfos,
+                    obsoleteAgents,
+                    trustMayHaveChanged);
+        }
+
         if (trustMayHaveChanged) {
             if (userIdOrAll == UserHandle.USER_ALL) {
                 updateTrustAll();
@@ -1044,7 +1181,7 @@
             parser = resolveInfo.serviceInfo.loadXmlMetaData(pm,
                     TrustAgentService.TRUST_AGENT_META_DATA);
             if (parser == null) {
-                Slog.w(TAG, "Can't find " + TrustAgentService.TRUST_AGENT_META_DATA + " meta-data");
+                Slogf.w(TAG, "Can't find %s meta-data", TrustAgentService.TRUST_AGENT_META_DATA);
                 return null;
             }
             Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
@@ -1056,7 +1193,7 @@
             }
             String nodeName = parser.getName();
             if (!"trust-agent".equals(nodeName)) {
-                Slog.w(TAG, "Meta-data does not start with trust-agent tag");
+                Slogf.w(TAG, "Meta-data does not start with trust-agent tag");
                 return null;
             }
             TypedArray sa = res
@@ -1075,7 +1212,11 @@
             if (parser != null) parser.close();
         }
         if (caughtException != null) {
-            Slog.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
+            Slogf.w(
+                    TAG,
+                    caughtException,
+                    "Error parsing : %s",
+                    resolveInfo.serviceInfo.packageName);
             return null;
         }
         if (cn == null) {
@@ -1242,13 +1383,18 @@
     // Agent dispatch and aggregation
 
     private boolean aggregateIsTrusted(int userId) {
+        if (DEBUG) Slogf.d(TAG, "aggregateIsTrusted(userId=%s)", userId);
         if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+            if (DEBUG) {
+                Slogf.d(TAG, "not trusted because trust not allowed for userId=%s", userId);
+            }
             return false;
         }
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
             if (info.userId == userId) {
                 if (info.agent.isTrusted()) {
+                    if (DEBUG) Slogf.d(TAG, "trusted by %s", info);
                     return true;
                 }
             }
@@ -1257,13 +1403,18 @@
     }
 
     private boolean aggregateIsTrustable(int userId) {
+        if (DEBUG) Slogf.d(TAG, "aggregateIsTrustable(userId=%s)", userId);
         if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+            if (DEBUG) {
+                Slogf.d(TAG, "not trustable because trust not allowed for userId=%s", userId);
+            }
             return false;
         }
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
             if (info.userId == userId) {
                 if (info.agent.isTrustable()) {
+                    if (DEBUG) Slogf.d(TAG, "trustable by %s", info);
                     return true;
                 }
             }
@@ -1328,20 +1479,31 @@
 
     private boolean aggregateIsTrustManaged(int userId) {
         if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+            if (DEBUG) {
+                Slogf.d(
+                        TAG,
+                        "trust not managed due to trust not being allowed for userId=%s",
+                        userId);
+            }
             return false;
         }
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
             if (info.userId == userId) {
                 if (info.agent.isManagingTrust()) {
+                    if (DEBUG) Slogf.d(TAG, "trust managed for userId=%s", userId);
                     return true;
                 }
             }
         }
+        if (DEBUG) Slogf.d(TAG, "trust not managed for userId=%s", userId);
         return false;
     }
 
     private void dispatchUnlockAttempt(boolean successful, int userId) {
+        if (DEBUG) {
+            Slogf.d(TAG, "dispatchUnlockAttempt(successful=%s, userId=%s)", successful, userId);
+        }
         if (successful) {
             mStrongAuthTracker.allowTrustFromUnlock(userId);
             // Allow the presence of trust on a successful unlock attempt to extend unlock
@@ -1359,8 +1521,11 @@
 
     private void dispatchUserRequestedUnlock(int userId, boolean dismissKeyguard) {
         if (DEBUG) {
-            Slog.d(TAG, "dispatchUserRequestedUnlock(user=" + userId + ", dismissKeyguard="
-                    + dismissKeyguard + ")");
+            Slogf.d(
+                    TAG,
+                    "dispatchUserRequestedUnlock(user=%s, dismissKeyguard=%s)",
+                    userId,
+                    dismissKeyguard);
         }
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
@@ -1372,7 +1537,7 @@
 
     private void dispatchUserMayRequestUnlock(int userId) {
         if (DEBUG) {
-            Slog.d(TAG, "dispatchUserMayRequestUnlock(user=" + userId + ")");
+            Slogf.d(TAG, "dispatchUserMayRequestUnlock(user=%s)", userId);
         }
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
@@ -1405,9 +1570,9 @@
         try {
             listener.onIsActiveUnlockRunningChanged(isRunning, userId);
         } catch (DeadObjectException e) {
-            Slog.d(TAG, "TrustListener dead while trying to notify Active Unlock running state");
+            Slogf.d(TAG, "TrustListener dead while trying to notify Active Unlock running state");
         } catch (RemoteException e) {
-            Slog.e(TAG, "Exception while notifying TrustListener.", e);
+            Slogf.e(TAG, "Exception while notifying TrustListener.", e);
         }
     }
 
@@ -1445,11 +1610,11 @@
                 mTrustListeners.get(i).onTrustChanged(
                         enabled, newlyUnlocked, userId, flags, trustGrantedMessages);
             } catch (DeadObjectException e) {
-                Slog.d(TAG, "Removing dead TrustListener.");
+                Slogf.d(TAG, "Removing dead TrustListener.");
                 mTrustListeners.remove(i);
                 i--;
             } catch (RemoteException e) {
-                Slog.e(TAG, "Exception while notifying TrustListener.", e);
+                Slogf.e(TAG, "Exception while notifying TrustListener.", e);
             }
         }
     }
@@ -1462,11 +1627,11 @@
             try {
                 mTrustListeners.get(i).onEnabledTrustAgentsChanged(userId);
             } catch (DeadObjectException e) {
-                Slog.d(TAG, "Removing dead TrustListener.");
+                Slogf.d(TAG, "Removing dead TrustListener.");
                 mTrustListeners.remove(i);
                 i--;
             } catch (RemoteException e) {
-                Slog.e(TAG, "Exception while notifying TrustListener.", e);
+                Slogf.e(TAG, "Exception while notifying TrustListener.", e);
             }
         }
     }
@@ -1479,11 +1644,11 @@
             try {
                 mTrustListeners.get(i).onTrustManagedChanged(managed, userId);
             } catch (DeadObjectException e) {
-                Slog.d(TAG, "Removing dead TrustListener.");
+                Slogf.d(TAG, "Removing dead TrustListener.");
                 mTrustListeners.remove(i);
                 i--;
             } catch (RemoteException e) {
-                Slog.e(TAG, "Exception while notifying TrustListener.", e);
+                Slogf.e(TAG, "Exception while notifying TrustListener.", e);
             }
         }
     }
@@ -1496,11 +1661,11 @@
             try {
                 mTrustListeners.get(i).onTrustError(message);
             } catch (DeadObjectException e) {
-                Slog.d(TAG, "Removing dead TrustListener.");
+                Slogf.d(TAG, "Removing dead TrustListener.");
                 mTrustListeners.remove(i);
                 i--;
             } catch (RemoteException e) {
-                Slog.e(TAG, "Exception while notifying TrustListener.", e);
+                Slogf.e(TAG, "Exception while notifying TrustListener.", e);
             }
         }
     }
@@ -1535,7 +1700,7 @@
                     && mFingerprintManager.hasEnrolledTemplates(userId)
                     && isWeakOrConvenienceSensor(
                             mFingerprintManager.getSensorProperties().get(0))) {
-                Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+                Slogf.i(TAG, "User is unlockable by non-strong fingerprint auth");
                 return true;
             }
 
@@ -1543,7 +1708,7 @@
                     && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
                     && mFaceManager.hasEnrolledTemplates(userId)
                     && isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
-                Slog.i(TAG, "User is unlockable by non-strong face auth");
+                Slogf.i(TAG, "User is unlockable by non-strong face auth");
                 return true;
             }
         }
@@ -1551,7 +1716,7 @@
         // Check whether it's possible for the device to be actively unlocked by a trust agent.
         if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
                 || (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
-            Slog.i(TAG, "User is unlockable by trust agent");
+            Slogf.i(TAG, "User is unlockable by trust agent");
             return true;
         }
 
@@ -1595,6 +1760,13 @@
     private final IBinder mService = new ITrustManager.Stub() {
         @Override
         public void reportUnlockAttempt(boolean authenticated, int userId) throws RemoteException {
+            if (DEBUG) {
+                Slogf.d(
+                        TAG,
+                        "reportUnlockAttempt(authenticated=%s, userId=%s)",
+                        authenticated,
+                        userId);
+            }
             enforceReportPermission();
             mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, authenticated ? 1 : 0, userId)
                     .sendToTarget();
@@ -1611,7 +1783,8 @@
         @Override
         public void reportUserMayRequestUnlock(int userId) throws RemoteException {
             enforceReportPermission();
-            mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId, /*arg2=*/ 0).sendToTarget();
+            mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId, /* arg2= */ 0)
+                    .sendToTarget();
         }
 
         @Override
@@ -1932,6 +2105,7 @@
         return new Handler(looper) {
             @Override
             public void handleMessage(Message msg) {
+                if (DEBUG) Slogf.d(TAG, "handler: %s", msg.what);
                 switch (msg.what) {
                     case MSG_REGISTER_LISTENER:
                         addListener((ITrustListener) msg.obj);
@@ -2002,8 +2176,24 @@
                         handleScheduleTrustTimeout(shouldOverride, timeoutType);
                         break;
                     case MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH:
+                        if (DEBUG) {
+                            Slogf.d(TAG, "REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH userId=%s", msg.arg1);
+                        }
                         TrustableTimeoutAlarmListener trustableAlarm =
                                 mTrustableTimeoutAlarmListenerForUser.get(msg.arg1);
+                        if (DEBUG) {
+                            if (trustableAlarm != null) {
+                                Slogf.d(
+                                        TAG,
+                                        "REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH trustable alarm "
+                                                + "isQueued=%s",
+                                        trustableAlarm.mIsQueued);
+                            } else {
+                                Slogf.d(
+                                        TAG,
+                                        "REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH no trustable alarm");
+                            }
+                        }
                         if (trustableAlarm != null && trustableAlarm.isQueued()) {
                             refreshTrustableTimers(msg.arg1);
                         }
@@ -2194,7 +2384,7 @@
             handleAlarm();
             // Only fire if trust can unlock.
             if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
-                if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
+                if (DEBUG) Slogf.d(TAG, "Revoking all trust because of trust timeout");
                 mLockPatternUtils.requireStrongAuth(
                         mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e24a597..2d2a88a86 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7644,6 +7644,8 @@
             // This could only happen when the window is removed from hierarchy. So do not keep its
             // reference anymore.
             mStartingWindow = null;
+            mStartingData = null;
+            mStartingSurface = null;
         }
         if (mChildren.size() == 0 && mVisibleSetFromTransferredStartingWindow) {
             // We set the visible state to true for the token from a transferred starting
@@ -8551,7 +8553,7 @@
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (!isLetterboxedForFixedOrientationAndAspectRatio()
+        if (Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio()
                 && !mLetterboxUiController.hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
@@ -8568,6 +8570,14 @@
                 computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
             }
         }
+        // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
+        // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+        // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
+        // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+        if (!Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio()
+                && !mInSizeCompatModeForBounds && !mLetterboxUiController.hasFullscreenOverride()) {
+            resolveAspectRatioRestriction(newParentConfiguration);
+        }
 
         if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
                 // In fullscreen, can be letterboxed for aspect ratio.
@@ -8903,7 +8913,11 @@
     }
 
     boolean isImmersiveMode(@NonNull Rect parentBounds) {
-        if (!mResolveConfigHint.mUseOverrideInsetsForConfig) {
+        if (!Flags.immersiveAppRepositioning()) {
+            return false;
+        }
+        if (!mResolveConfigHint.mUseOverrideInsetsForConfig
+                && mWmService.mFlags.mInsetsDecoupledConfiguration) {
             return false;
         }
         final Insets navBarInsets = mDisplayContent.getInsetsStateController()
@@ -9247,17 +9261,35 @@
             @NonNull CompatDisplayInsets compatDisplayInsets) {
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+        final Insets insets;
+        if (mResolveConfigHint.mUseOverrideInsetsForConfig) {
+            // TODO(b/343197837): Add test to verify SCM behaviour with new bound configuration
+            // Insets are decoupled from configuration by default from V+, use legacy
+            // compatibility behaviour for apps targeting SDK earlier than 35
+            // (see applySizeOverrideIfNeeded).
+            insets = Insets.of(mDisplayContent.getDisplayPolicy()
+                    .getDecorInsetsInfo(mDisplayContent.mDisplayFrames.mRotation,
+                            mDisplayContent.mDisplayFrames.mWidth,
+                            mDisplayContent.mDisplayFrames.mHeight).mOverrideNonDecorInsets);
+        } else {
+            insets = Insets.NONE;
+        }
 
         // When an activity needs to be letterboxed because of fixed orientation, use fixed
         // orientation bounds (stored in resolved bounds) instead of parent bounds since the
         // activity will be displayed within them even if it is in size compat mode. They should be
         // saved here before resolved bounds are overridden below.
-        final Rect containerBounds = isAspectRatioApplied()
+        final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
+                ? isAspectRatioApplied() : isLetterboxedForFixedOrientationAndAspectRatio();
+        final Rect containerBounds = useResolvedBounds
                 ? new Rect(resolvedBounds)
                 : newParentConfiguration.windowConfiguration.getBounds();
-        final Rect containerAppBounds = isAspectRatioApplied()
+        final Rect parentAppBounds =
+                newParentConfiguration.windowConfiguration.getAppBounds();
+        parentAppBounds.inset(insets);
+        final Rect containerAppBounds = useResolvedBounds
                 ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
-                : newParentConfiguration.windowConfiguration.getAppBounds();
+                : parentAppBounds;
 
         final int requestedOrientation = getRequestedConfigurationOrientation();
         final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9dc9ad4..6c48e95 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1931,6 +1931,9 @@
             if (td.getSystemBarsAppearance() == 0) {
                 td.setSystemBarsAppearance(atd.getSystemBarsAppearance());
             }
+            if (td.getTopOpaqueSystemBarsAppearance() == 0 && r.fillsParent()) {
+                td.setTopOpaqueSystemBarsAppearance(atd.getSystemBarsAppearance());
+            }
             if (td.getNavigationBarColor() == 0) {
                 td.setNavigationBarColor(atd.getNavigationBarColor());
                 td.setEnsureNavigationBarContrastWhenTransparent(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0bf1c88..a00b6fc4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1525,7 +1525,7 @@
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
             float[] outSizeCompatScale) {
-        outActiveControls.set(null);
+        outActiveControls.set(null, false /* copyControls */);
         int[] appOp = new int[1];
         final boolean isRoundedCornerOverlay = (attrs.privateFlags
                 & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
@@ -1927,7 +1927,7 @@
             displayContent.getInsetsStateController().updateAboveInsetsState(
                     false /* notifyInsetsChanged */);
 
-            outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
+            win.fillInsetsState(outInsetsState, true /* copySources */);
             getInsetsSourceControls(win, outActiveControls);
 
             if (win.mLayoutAttached) {
@@ -2317,7 +2317,7 @@
             InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
             Bundle outBundle, WindowRelayoutResult outRelayoutResult) {
         if (outActiveControls != null) {
-            outActiveControls.set(null);
+            outActiveControls.set(null, false /* copyControls */);
         }
         int result = 0;
         boolean configChanged = false;
@@ -2680,7 +2680,7 @@
             }
 
             if (outInsetsState != null) {
-                outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
+                win.fillInsetsState(outInsetsState, true /* copySources */);
             }
 
             ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2743,25 +2743,14 @@
     }
 
     private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) {
-        final InsetsSourceControl[] controls =
-                win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win);
-        if (controls != null) {
-            final int length = controls.length;
-            final InsetsSourceControl[] outControls = new InsetsSourceControl[length];
-            for (int i = 0; i < length; i++) {
-                // We will leave the critical section before returning the leash to the client,
-                // so we need to copy the leash to prevent others release the one that we are
-                // about to return.
-                if (controls[i] != null) {
-                    // This source control is an extra copy if the client is not local. By setting
-                    // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of
-                    // SurfaceControl.writeToParcel.
-                    outControls[i] = new InsetsSourceControl(controls[i]);
-                    outControls[i].setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
-                }
-            }
-            outArray.set(outControls);
-        }
+        // We will leave the critical section before returning the leash to the client,
+        // so we need to copy the leash to prevent others release the one that we are
+        // about to return.
+        win.fillInsetsSourceControls(outArray, true /* copyControls */);
+        // This source control is an extra copy if the client is not local. By setting
+        // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of
+        // SurfaceControl.writeToParcel.
+        outArray.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
     }
 
     private void tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6953c60..d1efc27 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -434,7 +434,10 @@
     /** @see #isLastConfigReportedToClient() */
     private boolean mLastConfigReportedToClient;
 
-    // TODO(b/339380439): Ensure to use the same object for IWindowSession#relayout
+    private final ClientWindowFrames mLastReportedFrames = new ClientWindowFrames();
+
+    private final InsetsState mLastReportedInsetsState = new InsetsState();
+
     private final InsetsSourceControl.Array mLastReportedActiveControls =
             new InsetsSourceControl.Array();
 
@@ -495,8 +498,6 @@
 
     private final WindowFrames mWindowFrames = new WindowFrames();
 
-    private final ClientWindowFrames mClientWindowFrames = new ClientWindowFrames();
-
     /**
      * List of rects where system gestures should be ignored.
      *
@@ -3650,8 +3651,10 @@
                 outFrames.attachedFrame.scale(mInvGlobalScale);
             }
         }
-
         outFrames.compatScale = getCompatScaleForClient();
+        if (mLastReportedFrames != outFrames) {
+            mLastReportedFrames.setTo(outFrames);
+        }
 
         // Note: in the cases where the window is tied to an activity, we should not send a
         // configuration update when the window has requested to be hidden. Doing so can lead to
@@ -3678,6 +3681,25 @@
         mLastConfigReportedToClient = true;
     }
 
+    void fillInsetsState(@NonNull InsetsState outInsetsState, boolean copySources) {
+        outInsetsState.set(getCompatInsetsState(), copySources);
+        if (outInsetsState != mLastReportedInsetsState) {
+            // No need to copy for the recorded.
+            mLastReportedInsetsState.set(outInsetsState, false /* copySources */);
+        }
+    }
+
+    void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray,
+            boolean copyControls) {
+        final InsetsSourceControl[] controls =
+                getDisplayContent().getInsetsStateController().getControlsForDispatch(this);
+        outArray.set(controls, copyControls);
+        if (outArray != mLastReportedActiveControls) {
+            // No need to copy for the recorded.
+            mLastReportedActiveControls.setTo(outArray, false /* copyControls */);
+        }
+    }
+
     void reportResized() {
         // If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now
         // since it will be destroyed anyway. This also prevents the client from receiving
@@ -3712,9 +3734,10 @@
 
         final int prevRotation = mLastReportedConfiguration
                 .getMergedConfiguration().windowConfiguration.getRotation();
-        fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
+        fillClientWindowFramesAndConfiguration(mLastReportedFrames, mLastReportedConfiguration,
                 mLastReportedActivityWindowInfo, true /* useLatestConfig */,
                 false /* relayoutVisible */);
+        fillInsetsState(mLastReportedInsetsState, false /* copySources */);
         final boolean syncRedraw = shouldSendRedrawForSync();
         final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers();
         final boolean reportDraw = syncRedraw || drawPending;
@@ -3734,8 +3757,8 @@
 
         if (Flags.bundleClientTransactionFlag()) {
             getProcess().scheduleClientTransactionItem(
-                    WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
-                            mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
+                    WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw,
+                            mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout,
                             alwaysConsumeSystemBars, displayId,
                             syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
                             mLastReportedActivityWindowInfo));
@@ -3743,8 +3766,8 @@
         } else {
             // TODO(b/301870955): cleanup after launch
             try {
-                mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
-                        getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
+                mClient.resized(mLastReportedFrames, reportDraw, mLastReportedConfiguration,
+                        mLastReportedInsetsState, forceRelayout, alwaysConsumeSystemBars, displayId,
                         syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
                         mLastReportedActivityWindowInfo);
                 onResizePostDispatched(drawPending, prevRotation, displayId);
@@ -3817,16 +3840,14 @@
         if (mRemoved) {
             return;
         }
-        final InsetsStateController stateController =
-                getDisplayContent().getInsetsStateController();
-        final InsetsState insetsState = getCompatInsetsState();
-        mLastReportedActiveControls.set(stateController.getControlsForDispatch(this));
+        fillInsetsState(mLastReportedInsetsState, false /* copySources */);
+        fillInsetsSourceControls(mLastReportedActiveControls, false /* copyControls */);
         if (Flags.insetsControlChangedItem()) {
             getProcess().scheduleClientTransactionItem(WindowStateInsetsControlChangeItem.obtain(
-                    mClient, insetsState, mLastReportedActiveControls));
+                    mClient, mLastReportedInsetsState, mLastReportedActiveControls));
         } else {
             try {
-                mClient.insetsControlChanged(insetsState, mLastReportedActiveControls);
+                mClient.insetsControlChanged(mLastReportedInsetsState, mLastReportedActiveControls);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
             }
diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp
index ddbc535..e42f7f8 100644
--- a/services/core/jni/BroadcastRadio/convert.cpp
+++ b/services/core/jni/BroadcastRadio/convert.cpp
@@ -433,7 +433,7 @@
                 gjni.AmBandDescriptor.clazz, gjni.AmBandDescriptor.cstor,
                 region, config.type, config.lowerLimit, config.upperLimit, spacing, am.stereo));
     } else {
-        ALOGE("Unsupported band type: %d", config.type);
+        ALOGE("Unsupported band type: %d", static_cast<int>(config.type));
         return nullptr;
     }
 }
@@ -451,7 +451,7 @@
         return make_javaref(env, env->NewObject(
                 gjni.AmBandConfig.clazz, gjni.AmBandConfig.cstor, descriptor.get()));
     } else {
-        ALOGE("Unsupported band type: %d", config.type);
+        ALOGE("Unsupported band type: %d", static_cast<int>(config.type));
         return nullptr;
     }
 }
@@ -539,9 +539,9 @@
                         item.clockValue.timezoneOffsetInMinutes);
                 break;
             default:
-                ALOGW("invalid metadata type %d", item.type);
+                ALOGW("invalid metadata type %d", static_cast<int>(item.type));
         }
-        ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, item.type);
+        ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, static_cast<int>(item.type));
     }
 
     return jMetadata;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index e763c9e..669a999 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1588,7 +1588,7 @@
     private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
         synchronized (mLock) {
             return mEnforcingAdmins.contains(userId)
-                    ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+                    ? new HashSet<>(mEnforcingAdmins.get(userId)) : Collections.emptySet();
         }
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 12050e1..01ff35f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1142,6 +1142,20 @@
     }
 
     @Test
+    public void test_createLocalExternalDisplay_displayManagementEnabled_doesNotCrash()
+            throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token)).thenReturn(null);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+    }
+
+    @Test
     public void test_createLocalExternalDisplay_displayManagementEnabled_shouldHaveDefaultGroup()
             throws Exception {
         FakeDisplay display = new FakeDisplay(PORT_A);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 52b33db..d4f0d5a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -593,6 +593,12 @@
         }
 
         @Override
+        public void onMagnificationSystemUIConnectionChanged(boolean connected)
+                throws RemoteException {
+
+        }
+
+        @Override
         public void onMagnificationChanged(int displayId, Region region, MagnificationConfig config)
                 throws RemoteException {
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 87fe6cf..0de5807 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -59,6 +59,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.FlakyTest;
 
+import com.android.compatibility.common.util.TestUtils;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityTraceManager;
@@ -76,6 +77,7 @@
  */
 public class MagnificationConnectionManagerTest {
 
+    private static final int WAIT_CONNECTION_TIMEOUT_SECOND = 1;
     private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
     private static final int SERVICE_ID = 1;
 
@@ -115,17 +117,19 @@
     private void stubSetConnection(boolean needDelay) {
         doAnswer((InvocationOnMock invocation) -> {
             final boolean connect = (Boolean) invocation.getArguments()[0];
-            // Simulates setConnection() called by another process.
+            // Use post to simulate setConnection() called by another process.
+            final Context context = ApplicationProvider.getApplicationContext();
             if (needDelay) {
-                final Context context = ApplicationProvider.getApplicationContext();
                 context.getMainThreadHandler().postDelayed(
                         () -> {
                             mMagnificationConnectionManager.setConnection(
                                     connect ? mMockConnection.getConnection() : null);
                         }, 10);
             } else {
-                mMagnificationConnectionManager.setConnection(
-                        connect ? mMockConnection.getConnection() : null);
+                context.getMainThreadHandler().post(() -> {
+                    mMagnificationConnectionManager.setConnection(
+                            connect ? mMockConnection.getConnection() : null);
+                });
             }
             return true;
         }).when(mMockStatusBarManagerInternal).requestMagnificationConnection(anyBoolean());
@@ -629,9 +633,10 @@
     }
 
     @Test
-    public void isConnected_requestConnection_expectedValue() throws RemoteException {
+    public void isConnected_requestConnection_expectedValue() throws Exception {
         mMagnificationConnectionManager.requestConnection(true);
-        assertTrue(mMagnificationConnectionManager.isConnected());
+        TestUtils.waitUntil("connection is not ready", WAIT_CONNECTION_TIMEOUT_SECOND,
+                () -> mMagnificationConnectionManager.isConnected());
 
         mMagnificationConnectionManager.requestConnection(false);
         assertFalse(mMagnificationConnectionManager.isConnected());
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index e756082..758c84a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.media.AudioDeviceAttributes;
@@ -37,6 +38,7 @@
 import android.media.AudioSystem;
 import android.media.IAudioDeviceVolumeDispatcher;
 import android.media.VolumeInfo;
+import android.os.PermissionEnforcer;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -98,7 +100,8 @@
 
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
-                mTestLooper.getLooper()) {
+                mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
+                mock(AudioServerPermissionProvider.class)) {
             @Override
             public int getDeviceForStream(int stream) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 3623012..2cb02bd 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -23,12 +23,14 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.VolumeInfo;
+import android.os.PermissionEnforcer;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -75,7 +77,8 @@
         mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase();
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
-                mTestLooper.getLooper()) {
+                mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
+                mock(AudioServerPermissionProvider.class)) {
             @Override
             public int getDeviceForStream(int stream) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
new file mode 100644
index 0000000..8d772ad
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.audio;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.media.permission.INativePermissionController;
+import com.android.media.permission.UidPackageState;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class AudioServerPermissionProviderTest {
+
+    // Class under test
+    private AudioServerPermissionProvider mPermissionProvider;
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock public INativePermissionController mMockPc;
+
+    @Mock public PackageState mMockPackageStateOne_10000_one;
+    @Mock public PackageState mMockPackageStateTwo_10001_two;
+    @Mock public PackageState mMockPackageStateThree_10000_one;
+    @Mock public PackageState mMockPackageStateFour_10000_three;
+    @Mock public PackageState mMockPackageStateFive_10001_four;
+    @Mock public PackageState mMockPackageStateSix_10000_two;
+
+    public List<UidPackageState> mInitPackageListExpected;
+
+    // Argument matcher which matches that the state is equal even if the package names are out of
+    // order (since they are logically a set).
+    public static final class UidPackageStateMatcher implements ArgumentMatcher<UidPackageState> {
+        private final int mUid;
+        private final List<String> mSortedPackages;
+
+        public UidPackageStateMatcher(int uid, List<String> packageNames) {
+            mUid = uid;
+            if (packageNames != null) {
+                mSortedPackages = new ArrayList(packageNames);
+                Collections.sort(mSortedPackages);
+            } else {
+                mSortedPackages = null;
+            }
+        }
+
+        public UidPackageStateMatcher(UidPackageState toMatch) {
+            this(toMatch.uid, toMatch.packageNames);
+        }
+
+        @Override
+        public boolean matches(UidPackageState state) {
+            if (state == null) return false;
+            if (state.uid != mUid) return false;
+            if ((state.packageNames == null) != (mSortedPackages == null)) return false;
+            var copy = new ArrayList(state.packageNames);
+            Collections.sort(copy);
+            return mSortedPackages.equals(copy);
+        }
+
+        @Override
+        public String toString() {
+            return "Matcher for UidState with uid: " + mUid + ": " + mSortedPackages;
+        }
+    }
+
+    public static final class PackageStateListMatcher
+            implements ArgumentMatcher<List<UidPackageState>> {
+
+        private final List<UidPackageState> mToMatch;
+
+        public PackageStateListMatcher(List<UidPackageState> toMatch) {
+            mToMatch = Objects.requireNonNull(toMatch);
+        }
+
+        @Override
+        public boolean matches(List<UidPackageState> other) {
+            if (other == null) return false;
+            if (other.size() != mToMatch.size()) return false;
+            for (int i = 0; i < mToMatch.size(); i++) {
+                var matcher = new UidPackageStateMatcher(mToMatch.get(i));
+                if (!matcher.matches(other.get(i))) return false;
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "Matcher for List<UidState> with uid: " + mToMatch;
+        }
+    }
+
+    @Before
+    public void setup() {
+        when(mMockPackageStateOne_10000_one.getAppId()).thenReturn(10000);
+        when(mMockPackageStateOne_10000_one.getPackageName()).thenReturn("com.package.one");
+
+        when(mMockPackageStateTwo_10001_two.getAppId()).thenReturn(10001);
+        when(mMockPackageStateTwo_10001_two.getPackageName()).thenReturn("com.package.two");
+
+        // Same state as the first is intentional, emulating multi-user
+        when(mMockPackageStateThree_10000_one.getAppId()).thenReturn(10000);
+        when(mMockPackageStateThree_10000_one.getPackageName()).thenReturn("com.package.one");
+
+        when(mMockPackageStateFour_10000_three.getAppId()).thenReturn(10000);
+        when(mMockPackageStateFour_10000_three.getPackageName()).thenReturn("com.package.three");
+
+        when(mMockPackageStateFive_10001_four.getAppId()).thenReturn(10001);
+        when(mMockPackageStateFive_10001_four.getPackageName()).thenReturn("com.package.four");
+
+        when(mMockPackageStateSix_10000_two.getAppId()).thenReturn(10000);
+        when(mMockPackageStateSix_10000_two.getPackageName()).thenReturn("com.package.two");
+    }
+
+    @Test
+    public void testInitialPackagePopulation() throws Exception {
+        var initPackageListData =
+                List.of(
+                        mMockPackageStateOne_10000_one,
+                        mMockPackageStateTwo_10001_two,
+                        mMockPackageStateThree_10000_one,
+                        mMockPackageStateFour_10000_three,
+                        mMockPackageStateFive_10001_four,
+                        mMockPackageStateSix_10000_two);
+        var expectedPackageList =
+                List.of(
+                        createUidPackageState(
+                                10000,
+                                List.of("com.package.one", "com.package.two", "com.package.three")),
+                        createUidPackageState(
+                                10001, List.of("com.package.two", "com.package.four")));
+
+        mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+        mPermissionProvider.onServiceStart(mMockPc);
+        verify(mMockPc)
+                .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList)));
+    }
+
+    @Test
+    public void testOnModifyPackageState_whenNewUid() throws Exception {
+        // 10000: one | 10001: two
+        var initPackageListData =
+                List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+        mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+        mPermissionProvider.onServiceStart(mMockPc);
+
+        // new uid, including user component
+        mPermissionProvider.onModifyPackageState(1_10002, "com.package.new", false /* isRemove */);
+
+        verify(mMockPc)
+                .updatePackagesForUid(
+                        argThat(new UidPackageStateMatcher(10002, List.of("com.package.new"))));
+        verify(mMockPc).updatePackagesForUid(any()); // exactly once
+    }
+
+    @Test
+    public void testOnModifyPackageState_whenRemoveUid() throws Exception {
+        // 10000: one | 10001: two
+        var initPackageListData =
+                List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+        mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+        mPermissionProvider.onServiceStart(mMockPc);
+
+        // Includes user-id
+        mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */);
+
+        verify(mMockPc).updatePackagesForUid(argThat(new UidPackageStateMatcher(10000, List.of())));
+        verify(mMockPc).updatePackagesForUid(any()); // exactly once
+    }
+
+    @Test
+    public void testOnModifyPackageState_whenUpdatedUidAddition() throws Exception {
+        // 10000: one | 10001: two
+        var initPackageListData =
+                List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+        mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+        mPermissionProvider.onServiceStart(mMockPc);
+
+        // Includes user-id
+        mPermissionProvider.onModifyPackageState(1_10000, "com.package.new", false /* isRemove */);
+
+        verify(mMockPc)
+                .updatePackagesForUid(
+                        argThat(
+                                new UidPackageStateMatcher(
+                                        10000, List.of("com.package.one", "com.package.new"))));
+        verify(mMockPc).updatePackagesForUid(any()); // exactly once
+    }
+
+    @Test
+    public void testOnModifyPackageState_whenUpdateUidRemoval() throws Exception {
+        // 10000: one, two | 10001: two
+        var initPackageListData =
+                List.of(
+                        mMockPackageStateOne_10000_one,
+                        mMockPackageStateTwo_10001_two,
+                        mMockPackageStateSix_10000_two);
+        mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+        mPermissionProvider.onServiceStart(mMockPc);
+
+        // Includes user-id
+        mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */);
+
+        verify(mMockPc)
+                .updatePackagesForUid(
+                        argThat(
+                                new UidPackageStateMatcher(
+                                        createUidPackageState(10000, List.of("com.package.two")))));
+        verify(mMockPc).updatePackagesForUid(any()); // exactly once
+    }
+
+    @Test
+    public void testOnServiceStart() throws Exception {
+        // 10000: one, two | 10001: two
+        var initPackageListData =
+                List.of(
+                        mMockPackageStateOne_10000_one,
+                        mMockPackageStateTwo_10001_two,
+                        mMockPackageStateSix_10000_two);
+        mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+        mPermissionProvider.onServiceStart(mMockPc);
+        mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */);
+        verify(mMockPc)
+                .updatePackagesForUid(
+                        argThat(new UidPackageStateMatcher(10000, List.of("com.package.two"))));
+
+        verify(mMockPc).updatePackagesForUid(any()); // exactly once
+        mPermissionProvider.onModifyPackageState(
+                1_10000, "com.package.three", false /* isRemove */);
+        verify(mMockPc)
+                .updatePackagesForUid(
+                        argThat(
+                                new UidPackageStateMatcher(
+                                        10000, List.of("com.package.two", "com.package.three"))));
+        verify(mMockPc, times(2)).updatePackagesForUid(any()); // exactly twice
+        // state is now 10000: two, three | 10001: two
+
+        // simulate restart of the service
+        mPermissionProvider.onServiceStart(null); // should handle null
+        var newMockPc = mock(INativePermissionController.class);
+        mPermissionProvider.onServiceStart(newMockPc);
+
+        var expectedPackageList =
+                List.of(
+                        createUidPackageState(
+                                10000, List.of("com.package.two", "com.package.three")),
+                        createUidPackageState(10001, List.of("com.package.two")));
+
+        verify(newMockPc)
+                .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList)));
+
+        verify(newMockPc, never()).updatePackagesForUid(any());
+        // updates should still work after restart
+        mPermissionProvider.onModifyPackageState(10001, "com.package.four", false /* isRemove */);
+        verify(newMockPc)
+                .updatePackagesForUid(
+                        argThat(
+                                new UidPackageStateMatcher(
+                                        10001, List.of("com.package.two", "com.package.four"))));
+        // exactly once
+        verify(newMockPc).updatePackagesForUid(any());
+    }
+
+    private static UidPackageState createUidPackageState(int uid, List<String> packages) {
+        var res = new UidPackageState();
+        res.uid = uid;
+        res.packageNames = packages;
+        return res;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 634877e..037c3c0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -66,6 +66,7 @@
     @Mock private AppOpsManager mMockAppOpsManager;
     @Mock private AudioPolicyFacade mMockAudioPolicy;
     @Mock private PermissionEnforcer mMockPermissionEnforcer;
+    @Mock private AudioServerPermissionProvider mMockPermissionProvider;
 
     // the class being unit-tested here
     private AudioService mAudioService;
@@ -86,7 +87,7 @@
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
-                mMockAppOpsManager, mMockPermissionEnforcer);
+                mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 8dfcc18..27b552f 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -22,11 +22,13 @@
 import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.os.PermissionEnforcer;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 
@@ -75,7 +77,8 @@
         mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase();
         mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
                 mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
-                mTestLooper.getLooper());
+                mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
+                mock(AudioServerPermissionProvider.class));
         mTestLooper.dispatchAll();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 23728db..8e34ee1 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -40,6 +40,7 @@
 import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
 
 import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -132,9 +133,12 @@
     @Mock
     private PermissionEnforcer mMockPermissionEnforcer;
     @Mock
+    private AudioServerPermissionProvider mMockPermissionProvider;
+    @Mock
     private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper;
 
-    private final AudioPolicyFacade mFakeAudioPolicy = lookbackAudio -> false;
+    @Mock
+    private AudioPolicyFacade mMockAudioPolicy;
 
     private AudioVolumeGroup mAudioMusicVolumeGroup;
 
@@ -153,9 +157,10 @@
                 SystemServerAdapter systemServer, SettingsAdapter settings,
                 AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
                 @Nullable Looper looper, AppOpsManager appOps,
-                @NonNull PermissionEnforcer enforcer) {
+                @NonNull PermissionEnforcer enforcer,
+                AudioServerPermissionProvider permissionProvider) {
             super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
-                    audioPolicy, looper, appOps, enforcer);
+                    audioPolicy, looper, appOps, enforcer, permissionProvider);
         }
 
         public void setDeviceForStream(int stream, int device) {
@@ -209,8 +214,9 @@
         mAm = mContext.getSystemService(AudioManager.class);
 
         mAudioService = new MyAudioService(mContext, mSpyAudioSystem, mSpySystemServer,
-                mSettingsAdapter, mAudioVolumeGroupHelper, mFakeAudioPolicy,
-                mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer);
+                mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
+                mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer,
+                mMockPermissionProvider);
 
         mTestLooper.dispatchAll();
         prepareAudioServiceState();
@@ -552,7 +558,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+    @RequiresFlagsDisabled({FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME, FLAG_ABS_VOLUME_INDEX_FIX})
     public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception {
         final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
         final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
@@ -607,6 +613,7 @@
 
     @Test
     @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+    @RequiresFlagsDisabled(FLAG_ABS_VOLUME_INDEX_FIX)
     public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception {
         final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
         final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 7dd1847..50cfa75 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -20,7 +20,6 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,6 +51,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.content.PackageMonitor;
@@ -70,6 +70,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
@@ -95,21 +96,21 @@
     private static final int DEFAULT_USER_ID = 0;
     private static final int WORK_PROFILE_USER_ID = 10;
     private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+    private static final int WORK_PROFILE_UID = Binder.getCallingUid() + 1000100;
     private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
     private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
     private static final LocaleList DEFAULT_LOCALES =
             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
     private static final Map<String, LocalesInfo> DEFAULT_PACKAGE_LOCALES_INFO_MAP = Map.of(
             DEFAULT_PACKAGE_NAME, new LocalesInfo(DEFAULT_LOCALE_TAGS, false));
-    private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA =
-            new SparseArray<>();
+    private final SparseArray<File> mStagedDataFiles = new SparseArray<>();
+    private File mArchivedPackageFile;
 
     private LocaleManagerBackupHelper mBackupHelper;
     private long mCurrentTimeMillis;
+    private Context mContext = spy(ApplicationProvider.getApplicationContext());
 
     @Mock
-    private Context mMockContext;
-    @Mock
     private PackageManager mMockPackageManager;
     @Mock
     private LocaleManagerService mMockLocaleManagerService;
@@ -138,23 +139,28 @@
 
     @Before
     public void setUp() throws Exception {
-        mMockContext = mock(Context.class);
         mMockPackageManager = mock(PackageManager.class);
         mMockLocaleManagerService = mock(LocaleManagerService.class);
         mMockDelegateAppLocalePackages = mock(SharedPreferences.class);
         mMockSpEditor = mock(SharedPreferences.Editor.class);
         SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
 
-        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(mMockPackageManager).when(mContext).getPackageManager();
         doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit();
 
         HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND);
         broadcastHandlerThread.start();
 
-        mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
-                mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
-                broadcastHandlerThread, mMockDelegateAppLocalePackages));
+        File file0 = new File(mContext.getCacheDir(), "file_user_0.txt");
+        File file10 = new File(mContext.getCacheDir(), "file_user_10.txt");
+        mStagedDataFiles.put(DEFAULT_USER_ID, file0);
+        mStagedDataFiles.put(WORK_PROFILE_USER_ID, file10);
+        mArchivedPackageFile = new File(mContext.getCacheDir(), "file_archived.txt");
+
+        mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mContext,
+            mMockLocaleManagerService, mMockPackageManager, mClock, broadcastHandlerThread,
+                mStagedDataFiles, mArchivedPackageFile, mMockDelegateAppLocalePackages));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
         mUserMonitor = mBackupHelper.getUserMonitor();
@@ -165,7 +171,16 @@
 
     @After
     public void tearDown() throws Exception {
-        STAGE_DATA.clear();
+        for (int i = 0; i < mStagedDataFiles.size(); i++) {
+            int userId = mStagedDataFiles.keyAt(i);
+            File file = mStagedDataFiles.get(userId);
+            SharedPreferences sp = mContext.getSharedPreferences(file, Context.MODE_PRIVATE);
+            sp.edit().clear().commit();
+            if (file.exists()) {
+                file.delete();
+            }
+        }
+        mStagedDataFiles.clear();
     }
 
     @Test
@@ -543,17 +558,21 @@
         mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle);
         mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle);
 
+        checkArchivedFileExists();
+
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
 
         setUpPackageInstalled(pkgNameA);
 
-        mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
+        mBackupHelper.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
                 .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+        checkArchivedFileExists();
+
 
         mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
 
@@ -565,11 +584,12 @@
 
         setUpPackageInstalled(pkgNameB);
 
-        mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
+        mBackupHelper.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
                 .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+        checkArchivedFileDoesNotExist();
 
         mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
 
@@ -723,7 +743,7 @@
         Intent intent = new Intent();
         intent.setAction(Intent.ACTION_USER_REMOVED);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID);
-        mUserMonitor.onReceive(mMockContext, intent);
+        mUserMonitor.onReceive(mContext, intent);
 
         // Stage data should be removed only for DEFAULT_USER_ID.
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
@@ -732,6 +752,72 @@
     }
 
     @Test
+    public void testRestore_multipleProfile_restoresFromStage_ArchiveEnabled() throws Exception {
+        final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
+        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
+        final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream();
+        String anotherPackage = "com.android.anotherapp";
+        String anotherLangTags = "mr,zh";
+        LocalesInfo localesInfo = new LocalesInfo(anotherLangTags, true);
+        HashMap<String, LocalesInfo> pkgLocalesMapWorkProfile = new HashMap<>();
+        pkgLocalesMapWorkProfile.put(anotherPackage, localesInfo);
+        writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
+        // DEFAULT_PACKAGE_NAME is NOT installed on the device.
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+        setUpPackageNotInstalled(anotherPackage);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(anotherPackage, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+        mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, bundle);
+        mPackageMonitor.onPackageAddedWithExtras(anotherPackage, WORK_PROFILE_UID, bundle);
+
+        checkArchivedFileExists();
+
+        mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID);
+        mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(),
+                WORK_PROFILE_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+        verifyStageDataForUser(pkgLocalesMapWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
+
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        mBackupHelper.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS), false, FrameworkStatsLog
+                        .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+        checkArchivedFileExists();
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false,
+                false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+
+        setUpPackageInstalled(anotherPackage);
+        mBackupHelper.onPackageUpdateFinished(anotherPackage, WORK_PROFILE_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(anotherPackage,
+                WORK_PROFILE_USER_ID,
+                LocaleList.forLanguageTags(anotherLangTags), true, FrameworkStatsLog
+                        .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+        checkArchivedFileDoesNotExist();
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, anotherPackage, true, false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+            new ArraySet<>(Arrays.asList(anotherPackage)));
+        checkStageDataDoesNotExist(WORK_PROFILE_USER_ID);
+    }
+
+    @Test
     public void testPackageRemoved_noInfoInSp() throws Exception {
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
@@ -858,10 +944,22 @@
 
     private void verifyStageDataForUser(Map<String, LocalesInfo> expectedPkgLocalesMap,
             long expectedCreationTimeMillis, int userId) {
-        LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId);
-        assertNotNull(stagedDataForUser);
-        assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis);
-        verifyStageData(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
+        SharedPreferences sp = mContext.getSharedPreferences(mStagedDataFiles.get(userId),
+                Context.MODE_PRIVATE);
+        assertTrue(sp.getAll().size() > 0);
+        assertEquals(expectedCreationTimeMillis, sp.getLong("staged_data_time", -1));
+        verifyStageData(expectedPkgLocalesMap, sp);
+    }
+
+    private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap,
+            SharedPreferences sp) {
+        for (String pkg : expectedPkgLocalesMap.keySet()) {
+            assertTrue(!sp.getString(pkg, "").isEmpty());
+            String[] info = sp.getString(pkg, "").split(" s:");
+            assertEquals(expectedPkgLocalesMap.get(pkg).mLocales, info[0]);
+            assertEquals(expectedPkgLocalesMap.get(pkg).mSetFromDelegate,
+                    Boolean.parseBoolean(info[1]));
+        }
     }
 
     private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap,
@@ -875,11 +973,19 @@
         }
     }
 
-    private static void checkStageDataExists(int userId) {
-        assertNotNull(STAGE_DATA.get(userId));
+    private void checkStageDataExists(int userId) {
+        assertTrue(mStagedDataFiles.get(userId) != null && mStagedDataFiles.get(userId).exists());
     }
 
-    private static void checkStageDataDoesNotExist(int userId) {
-        assertNull(STAGE_DATA.get(userId));
+    private void checkStageDataDoesNotExist(int userId) {
+        assertTrue(mStagedDataFiles.get(userId) == null || !mStagedDataFiles.get(userId).exists());
     }
-}
+
+    private void checkArchivedFileExists() {
+        assertTrue(mArchivedPackageFile.exists());
+    }
+
+    private void checkArchivedFileDoesNotExist() {
+        assertTrue(!mArchivedPackageFile.exists());
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index 9f7cbe3..b46902d 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -22,6 +22,7 @@
 import android.os.HandlerThread;
 import android.util.SparseArray;
 
+import java.io.File;
 import java.time.Clock;
 
 /**
@@ -33,9 +34,9 @@
     ShadowLocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
             PackageManager packageManager, Clock clock,
-            SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
-            HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
-        super(context, localeManagerService, packageManager, clock, stagedData,
-                broadcastHandlerThread, delegateAppLocalePackages);
+            HandlerThread broadcastHandlerThread, SparseArray<File> stagedDataFiles,
+            File archivedPackagesFile, SharedPreferences delegateAppLocalePackages) {
+        super(context, localeManagerService, packageManager, clock, broadcastHandlerThread,
+                stagedDataFiles, archivedPackagesFile, delegateAppLocalePackages);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4a9760b..e91fd37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2726,6 +2726,19 @@
         assertNoStartingWindow(activity);
     }
 
+    @Test
+    public void testPostCleanupStartingWindow() {
+        registerTestStartingWindowOrganizer();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity.addStartingWindow(mPackageName, android.R.style.Theme, null, true, true, false,
+                true, false, false, false);
+        waitUntilHandlersIdle();
+        assertHasStartingWindow(activity);
+        // Simulate Shell remove starting window actively.
+        activity.mStartingWindow.removeImmediately();
+        assertNoStartingWindow(activity);
+    }
+
     private void testLegacySplashScreen(int targetSdk, int verifyType) {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity.mTargetSdk = targetSdk;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 96ddfe8..7ced9d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -107,6 +107,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.provider.DeviceConfig;
@@ -401,6 +402,7 @@
     // TODO(b/333663877): Enable test after fix
     @Test
     @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION})
+    @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING)
     public void testRepositionLandscapeImmersiveAppWithDisplayCutout() {
         final int dw = 2100;
         final int dh = 2000;
@@ -4059,6 +4061,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING)
     public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() {
         assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */);
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1d01420..a7dbecb 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -794,9 +794,8 @@
                 if (curService != null && !curService.isEmpty()) {
                     try {
                         serviceComponent = ComponentName.unflattenFromString(curService);
-                        serviceInfo = AppGlobals.getPackageManager()
-                                .getServiceInfo(serviceComponent, 0, mCurUser);
-                    } catch (RuntimeException | RemoteException e) {
+                        serviceInfo = getValidVoiceInteractionServiceInfo(serviceComponent);
+                    } catch (RuntimeException e) {
                         Slog.wtf(TAG, "Bad voice interaction service name " + curService, e);
                         serviceComponent = null;
                         serviceInfo = null;
@@ -834,6 +833,27 @@
             }
         }
 
+        @Nullable
+        private ServiceInfo getValidVoiceInteractionServiceInfo(
+                @Nullable ComponentName serviceComponent) {
+            if (serviceComponent == null) {
+                return null;
+            }
+            List<ResolveInfo> services = queryInteractorServices(
+                    mCurUser, serviceComponent.getPackageName());
+            for (int i = 0; i < services.size(); i++) {
+                ResolveInfo service = services.get(i);
+                VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo(
+                        mContext.getPackageManager(), service.serviceInfo);
+                ServiceInfo candidateInfo = info.getServiceInfo();
+                if (candidateInfo != null
+                        && candidateInfo.getComponentName().equals(serviceComponent)) {
+                    return candidateInfo;
+                }
+            }
+            return null;
+        }
+
         private List<ResolveInfo> queryInteractorServices(
                 @UserIdInt int user,
                 @Nullable String packageName) {
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index d5db612..65e8e13 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -555,10 +555,11 @@
          * Set the device's carrier restriction status
          *
          * @param carrierRestrictionStatus device restriction status
-         * @hide
          */
         public @NonNull
-        Builder setCarrierRestrictionStatus(int carrierRestrictionStatus) {
+        @FlaggedApi(Flags.FLAG_SET_CARRIER_RESTRICTION_STATUS)
+        Builder setCarrierRestrictionStatus(
+                @CarrierRestrictionStatus int carrierRestrictionStatus) {
             mRules.mCarrierRestrictionStatus = carrierRestrictionStatus;
             return this;
         }
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index 2cdf542..e09fbf6 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -20,17 +20,65 @@
     // all of the 'license_kinds' from "frameworks_base_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
+    default_team: "trendy_team_windowing_sdk",
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "FlickerTestsOther",
+filegroup {
+    name: "FlickerTestsOtherCommon-src",
+    srcs: ["src/**/ActivityEmbeddingTestBase.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsOtherOpen-src",
+    srcs: ["src/**/open/*"],
+}
+
+filegroup {
+    name: "FlickerTestsOtherRotation-src",
+    srcs: ["src/**/rotation/*"],
+}
+
+java_library {
+    name: "FlickerTestsOtherCommon",
+    defaults: ["FlickerTestsDefault"],
+    srcs: [":FlickerTestsOtherCommon-src"],
+    static_libs: ["FlickerTestsBase"],
+}
+
+java_defaults {
+    name: "FlickerTestsOtherDefaults",
     defaults: ["FlickerTestsDefault"],
     manifest: "AndroidManifest.xml",
     package_name: "com.android.server.wm.flicker",
     instrumentation_target_package: "com.android.server.wm.flicker",
     test_config_template: "AndroidTestTemplate.xml",
-    srcs: ["src/**/*"],
-    static_libs: ["FlickerTestsBase"],
+    static_libs: [
+        "FlickerTestsBase",
+        "FlickerTestsOtherCommon",
+    ],
     data: ["trace_config/*"],
 }
+
+android_test {
+    name: "FlickerTestsOtherOpen",
+    defaults: ["FlickerTestsOtherDefaults"],
+    srcs: [":FlickerTestsOtherOpen-src"],
+}
+
+android_test {
+    name: "FlickerTestsOtherRotation",
+    defaults: ["FlickerTestsOtherDefaults"],
+    srcs: [":FlickerTestsOtherRotation-src"],
+}
+
+android_test {
+    name: "FlickerTestsOther",
+    defaults: ["FlickerTestsOtherDefaults"],
+    srcs: ["src/**/*"],
+    exclude_srcs: [
+        ":FlickerTestsOtherOpen-src",
+        ":FlickerTestsOtherRotation-src",
+        ":FlickerTestsOtherCommon-src",
+    ],
+}
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
index 8a241de..209a14b 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.service
 
 import android.app.Instrumentation
+import android.platform.test.rule.DisableNotificationCooldownSettingRule
 import android.platform.test.rule.NavigationModeRule
 import android.platform.test.rule.PressHomeRule
 import android.platform.test.rule.UnlockScreenRule
@@ -48,6 +49,7 @@
                     clearCacheAfterParsing = false
                 )
             )
+            .around(DisableNotificationCooldownSettingRule())
             .around(PressHomeRule())
     }
 }
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index 3538949c..ccc3683 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -39,6 +39,10 @@
     defaults: ["FlickerTestsDefault"],
     manifest: "AndroidManifest.xml",
     test_config_template: "AndroidTestTemplate.xml",
+    test_suites: [
+        "device-tests",
+        "device-platinum-tests",
+    ],
     srcs: ["src/**/*"],
     static_libs: ["FlickerTestsBase"],
     data: ["trace_config/*"],
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index dc50135..ed6e8df 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -23,6 +23,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.traces.component.ComponentNameMatcher
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import org.junit.FixMethodOrder
@@ -77,6 +78,7 @@
 
     @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
+    @FlakyTest(bugId = 330486656)
     @Presubmit
     @Test
     fun imeAppLayerIsAlwaysVisible() {
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index c29e71c..07fc230 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.DisableNotificationCooldownSettingRule
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.FlickerTestData
@@ -37,6 +38,7 @@
 import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
 import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
 import org.junit.Assume
+import org.junit.ClassRule
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
@@ -208,5 +210,10 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        /** Ensures that posted notifications will alert and HUN even just after boot. */
+        @ClassRule
+        @JvmField
+        val disablenotificationCooldown = DisableNotificationCooldownSettingRule()
     }
 }
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index a8b383c..093923f 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -101,8 +101,8 @@
     private static final String OBSERVER_NAME_2 = "observer2";
     private static final String OBSERVER_NAME_3 = "observer3";
     private static final String OBSERVER_NAME_4 = "observer4";
-    private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(10);
-    private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(50);
+    private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
+    private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -1453,8 +1453,7 @@
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
 
-        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS
-                - TimeUnit.MINUTES.toMillis(1));
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS);
 
         // The first failure will be outside the threshold.
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
@@ -1713,9 +1712,6 @@
             watchdog.onPackageFailure(packages, failureReason);
         }
         mTestLooper.dispatchAll();
-        if (Flags.recoverabilityDetection()) {
-            moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
-        }
     }
 
     private PackageWatchdog createWatchdog() {
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
index 30cf345..2f6c0dd 100644
--- a/tests/TrustTests/AndroidManifest.xml
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -78,6 +78,7 @@
                 <action android:name="android.service.trust.TrustAgentService" />
             </intent-filter>
         </service>
+
         <service
             android:name=".IsActiveUnlockRunningTrustAgent"
             android:exported="true"
@@ -88,6 +89,16 @@
             </intent-filter>
         </service>
 
+        <service
+            android:name=".UnlockAttemptTrustAgent"
+            android:exported="true"
+            android:label="Test Agent"
+            android:permission="android.permission.BIND_TRUST_AGENT">
+            <intent-filter>
+                <action android:name="android.service.trust.TrustAgentService" />
+            </intent-filter>
+        </service>
+
     </application>
 
     <!--  self-instrumenting test package. -->
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
new file mode 100644
index 0000000..2c9361d
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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.trust.test
+
+import android.app.trust.TrustManager
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TestTrustListener
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for the impacts of reporting unlock attempts.
+ *
+ * atest TrustTests:UnlockAttemptTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UnlockAttemptTest {
+    private val context = getApplicationContext<Context>()
+    private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+    private val userId = context.userId
+    private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+    private val screenLockRule = ScreenLockRule(requireStrongAuth = true)
+    private val lockStateTrackingRule = LockStateTrackingRule()
+    private val trustAgentRule =
+        TrustAgentRule<UnlockAttemptTrustAgent>(startUnlocked = false, startEnabled = false)
+
+    private val trustListener = UnlockAttemptTrustListener()
+    private val agent get() = trustAgentRule.agent
+
+    @get:Rule
+    val rule: RuleChain =
+        RuleChain.outerRule(activityScenarioRule)
+            .around(screenLockRule)
+            .around(lockStateTrackingRule)
+            .around(trustAgentRule)
+
+    @Before
+    fun setUp() {
+        trustManager.registerTrustListener(trustListener)
+    }
+
+    @Test
+    fun successfulUnlockAttempt_allowsTrustAgentToStart() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
+            trustAgentRule.enableTrustAgent()
+
+            triggerSuccessfulUnlock()
+
+            trustAgentRule.verifyAgentIsRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
+        }
+
+    @Test
+    fun successfulUnlockAttempt_notifiesTrustAgent() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldSuccessfulCount = agent.successfulUnlockCallCount
+            val oldFailedCount = agent.failedUnlockCallCount
+
+            triggerSuccessfulUnlock()
+
+            assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount + 1)
+            assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount)
+        }
+
+    @Test
+    fun successfulUnlockAttempt_notifiesTrustListenerOfManagedTrust() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
+
+            triggerSuccessfulUnlock()
+
+            assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
+                oldTrustManagedChangedCount + 1
+            )
+        }
+
+    @Test
+    fun failedUnlockAttempt_doesNotAllowTrustAgentToStart() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
+            trustAgentRule.enableTrustAgent()
+
+            triggerFailedUnlock()
+
+            trustAgentRule.ensureAgentIsNotRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
+        }
+
+    @Test
+    fun failedUnlockAttempt_notifiesTrustAgent() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldSuccessfulCount = agent.successfulUnlockCallCount
+            val oldFailedCount = agent.failedUnlockCallCount
+
+            triggerFailedUnlock()
+
+            assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount)
+            assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount + 1)
+        }
+
+    @Test
+    fun failedUnlockAttempt_doesNotNotifyTrustListenerOfManagedTrust() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
+
+            triggerFailedUnlock()
+
+            assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
+                oldTrustManagedChangedCount
+            )
+        }
+
+    private fun runUnlockAttemptTest(
+        enableAndVerifyTrustAgent: Boolean,
+        managingTrust: Boolean,
+        testBlock: () -> Unit,
+    ) {
+        if (enableAndVerifyTrustAgent) {
+            Log.i(TAG, "Triggering successful unlock")
+            triggerSuccessfulUnlock()
+            Log.i(TAG, "Enabling and waiting for trust agent")
+            trustAgentRule.enableAndVerifyTrustAgentIsRunning(
+                MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START
+            )
+            Log.i(TAG, "Managing trust: $managingTrust")
+            agent.setManagingTrust(managingTrust)
+            await()
+        }
+        testBlock()
+    }
+
+    private fun triggerSuccessfulUnlock() {
+        screenLockRule.successfulScreenLockAttempt()
+        trustAgentRule.reportSuccessfulUnlock()
+        await()
+    }
+
+    private fun triggerFailedUnlock() {
+        screenLockRule.failedScreenLockAttempt()
+        trustAgentRule.reportFailedUnlock()
+        await()
+    }
+
+    companion object {
+        private const val TAG = "UnlockAttemptTest"
+        private fun await(millis: Long = 500) = Thread.sleep(millis)
+        private const val MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START = 10000L
+    }
+}
+
+class UnlockAttemptTrustAgent : BaseTrustAgentService() {
+    var successfulUnlockCallCount: Long = 0
+        private set
+    var failedUnlockCallCount: Long = 0
+        private set
+
+    override fun onUnlockAttempt(successful: Boolean) {
+        super.onUnlockAttempt(successful)
+        if (successful) {
+            successfulUnlockCallCount++
+        } else {
+            failedUnlockCallCount++
+        }
+    }
+}
+
+private class UnlockAttemptTrustListener : TestTrustListener() {
+    var enabledTrustAgentsChangedCount = mutableMapOf<Int, Int>()
+    var onTrustManagedChangedCount = mutableMapOf<Int, Int>()
+
+    override fun onEnabledTrustAgentsChanged(userId: Int) {
+        enabledTrustAgentsChangedCount.compute(userId) { _: Int, curr: Int? ->
+            if (curr == null) 0 else curr + 1
+        }
+    }
+
+    data class TrustChangedParams(
+        val enabled: Boolean,
+        val newlyUnlocked: Boolean,
+        val userId: Int,
+        val flags: Int,
+        val trustGrantedMessages: MutableList<String>?
+    )
+
+    val onTrustChangedCalls = mutableListOf<TrustChangedParams>()
+
+    override fun onTrustChanged(
+        enabled: Boolean,
+        newlyUnlocked: Boolean,
+        userId: Int,
+        flags: Int,
+        trustGrantedMessages: MutableList<String>
+    ) {
+        onTrustChangedCalls += TrustChangedParams(
+            enabled, newlyUnlocked, userId, flags, trustGrantedMessages
+        )
+    }
+
+    override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
+        onTrustManagedChangedCount.compute(userId) { _: Int, curr: Int? ->
+            if (curr == null) 0 else curr + 1
+        }
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
index f1edca3..1ccdcc6 100644
--- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -24,6 +24,8 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiDevice
 import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
 import com.android.internal.widget.LockscreenCredential
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.rules.TestRule
@@ -32,13 +34,18 @@
 
 /**
  * Sets a screen lock on the device for the duration of the test.
+ *
+ * @param requireStrongAuth Whether a strong auth is required at the beginning.
+ * If true, trust agents will not be available until the user verifies their credentials.
  */
-class ScreenLockRule : TestRule {
+class ScreenLockRule(val requireStrongAuth: Boolean = false) : TestRule {
     private val context: Context = getApplicationContext()
+    private val userId = context.userId
     private val uiDevice = UiDevice.getInstance(getInstrumentation())
     private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
     private val lockPatternUtils = LockPatternUtils(context)
     private var instantLockSavedValue = false
+    private var strongAuthSavedValue: Int = 0
 
     override fun apply(base: Statement, description: Description) = object : Statement() {
         override fun evaluate() {
@@ -46,10 +53,12 @@
             dismissKeyguard()
             setScreenLock()
             setLockOnPowerButton()
+            configureStrongAuthState()
 
             try {
                 base.evaluate()
             } finally {
+                restoreStrongAuthState()
                 removeScreenLock()
                 revertLockOnPowerButton()
                 dismissKeyguard()
@@ -57,6 +66,22 @@
         }
     }
 
+    private fun configureStrongAuthState() {
+        strongAuthSavedValue = lockPatternUtils.getStrongAuthForUser(userId)
+        if (requireStrongAuth) {
+            Log.d(TAG, "Triggering strong auth due to simulated lockdown")
+            lockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, userId)
+            wait("strong auth required after lockdown") {
+                lockPatternUtils.getStrongAuthForUser(userId) ==
+                        STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+            }
+        }
+    }
+
+    private fun restoreStrongAuthState() {
+        lockPatternUtils.requireStrongAuth(strongAuthSavedValue, userId)
+    }
+
     private fun verifyNoScreenLockAlreadySet() {
         assertWithMessage("Screen Lock must not already be set on device")
                 .that(lockPatternUtils.isSecure(context.userId))
@@ -82,6 +107,22 @@
         }
     }
 
+    fun successfulScreenLockAttempt() {
+        lockPatternUtils.verifyCredential(LockscreenCredential.createPin(PIN), context.userId, 0)
+        lockPatternUtils.userPresent(context.userId)
+        wait("strong auth not required") {
+            lockPatternUtils.getStrongAuthForUser(context.userId) == STRONG_AUTH_NOT_REQUIRED
+        }
+    }
+
+    fun failedScreenLockAttempt() {
+        lockPatternUtils.verifyCredential(
+            LockscreenCredential.createPin(WRONG_PIN),
+            context.userId,
+            0
+        )
+    }
+
     private fun setScreenLock() {
         lockPatternUtils.setLockCredential(
                 LockscreenCredential.createPin(PIN),
@@ -121,5 +162,6 @@
     companion object {
         private const val TAG = "ScreenLockRule"
         private const val PIN = "0000"
+        private const val WRONG_PIN = "0001"
     }
 }
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
index 18bc029..404c6d9 100644
--- a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -20,14 +20,15 @@
 import android.content.ComponentName
 import android.content.Context
 import android.trust.BaseTrustAgentService
+import android.trust.test.lib.TrustAgentRule.Companion.invoke
 import android.util.Log
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import com.android.internal.widget.LockPatternUtils
 import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.KClass
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
-import kotlin.reflect.KClass
 
 /**
  * Enables a trust agent and causes the system service to bind to it.
@@ -37,7 +38,9 @@
  * @constructor Creates the rule. Do not use; instead, use [invoke].
  */
 class TrustAgentRule<T : BaseTrustAgentService>(
-    private val serviceClass: KClass<T>
+    private val serviceClass: KClass<T>,
+    private val startUnlocked: Boolean,
+    private val startEnabled: Boolean,
 ) : TestRule {
     private val context: Context = getApplicationContext()
     private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
@@ -48,11 +51,18 @@
     override fun apply(base: Statement, description: Description) = object : Statement() {
         override fun evaluate() {
             verifyTrustServiceRunning()
-            unlockDeviceWithCredential()
-            enableTrustAgent()
+            if (startUnlocked) {
+                reportSuccessfulUnlock()
+            } else {
+                Log.i(TAG, "Trust manager not starting in unlocked state")
+            }
 
             try {
-                verifyAgentIsRunning()
+                if (startEnabled) {
+                    enableAndVerifyTrustAgentIsRunning()
+                } else {
+                    Log.i(TAG, "Trust agent ${serviceClass.simpleName} not enabled")
+                }
                 base.evaluate()
             } finally {
                 disableTrustAgent()
@@ -64,12 +74,22 @@
         assertWithMessage("Trust service is not running").that(trustManager).isNotNull()
     }
 
-    private fun unlockDeviceWithCredential() {
-        Log.d(TAG, "Unlocking device with credential")
+    fun reportSuccessfulUnlock() {
+        Log.i(TAG, "Reporting successful unlock")
         trustManager.reportUnlockAttempt(true, context.userId)
     }
 
-    private fun enableTrustAgent() {
+    fun reportFailedUnlock() {
+        Log.i(TAG, "Reporting failed unlock")
+        trustManager.reportUnlockAttempt(false, context.userId)
+    }
+
+    fun enableAndVerifyTrustAgentIsRunning(maxWait: Long = 30000L) {
+        enableTrustAgent()
+        verifyAgentIsRunning(maxWait)
+    }
+
+    fun enableTrustAgent() {
         val componentName = ComponentName(context, serviceClass.java)
         val userId = context.userId
         Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId")
@@ -79,12 +99,18 @@
         lockPatternUtils.setEnabledTrustAgents(agents, userId)
     }
 
-    private fun verifyAgentIsRunning() {
-        wait("${serviceClass.simpleName} to be running") {
+    fun verifyAgentIsRunning(maxWait: Long = 30000L) {
+        wait("${serviceClass.simpleName} to be running", maxWait) {
             BaseTrustAgentService.instance(serviceClass) != null
         }
     }
 
+    fun ensureAgentIsNotRunning(window: Long = 30000L) {
+        ensure("${serviceClass.simpleName} is not running", window) {
+            BaseTrustAgentService.instance(serviceClass) == null
+        }
+    }
+
     private fun disableTrustAgent() {
         val componentName = ComponentName(context, serviceClass.java)
         val userId = context.userId
@@ -97,13 +123,23 @@
 
     companion object {
         /**
-         * Creates a new rule for the specified agent class. Example usage:
+         * Creates a new rule for the specified agent class. Starts with the device unlocked and
+         * the trust agent enabled. Example usage:
          * ```
          *   @get:Rule val rule = TrustAgentRule<MyTestAgent>()
          * ```
+         *
+         * Also supports setting different device lock and trust agent enablement states:
+         * ```
+         *   @get:Rule val rule = TrustAgentRule<MyTestAgent>(startUnlocked = false, startEnabled = false)
+         * ```
          */
-        inline operator fun <reified T : BaseTrustAgentService> invoke() =
-            TrustAgentRule(T::class)
+        inline operator fun <reified T : BaseTrustAgentService> invoke(
+            startUnlocked: Boolean = true,
+            startEnabled: Boolean = true,
+        ) =
+            TrustAgentRule(T::class, startUnlocked, startEnabled)
+
 
         private const val TAG = "TrustAgentRule"
     }
diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
similarity index 63%
rename from tests/TrustTests/src/android/trust/test/lib/utils.kt
rename to tests/TrustTests/src/android/trust/test/lib/Utils.kt
index e047202..3b32b47 100644
--- a/tests/TrustTests/src/android/trust/test/lib/utils.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
@@ -39,7 +39,7 @@
 ) {
     var waited = 0L
     var count = 0
-    while (!conditionFunction.invoke(count)) {
+    while (!conditionFunction(count)) {
         assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description")
             .that(waited <= maxWait)
             .isTrue()
@@ -49,3 +49,34 @@
         Thread.sleep(rate)
     }
 }
+
+/**
+ * Ensures that [conditionFunction] is true with a failed assertion if it is not within [window]
+ * ms.
+ *
+ * The condition function can perform additional logic (for example, logging or attempting to make
+ * the condition become true).
+ *
+ * @param conditionFunction function which takes the attempt count & returns whether the condition
+ *                          is met
+ */
+internal fun ensure(
+    description: String? = null,
+    window: Long = 30000L,
+    rate: Long = 50L,
+    conditionFunction: (count: Int) -> Boolean
+) {
+    var waited = 0L
+    var count = 0
+    while (waited <= window) {
+        assertWithMessage("Condition failed within $window ms: $description").that(
+                conditionFunction(
+                    count
+                )
+            ).isTrue()
+        waited += rate
+        count++
+        Log.i(TAG, "Ensuring $description ($waited/$window) #$count")
+        Thread.sleep(rate)
+    }
+}