Merge "Add debug logging to sfps indicator code" into main
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf20..f6213b9 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -31,6 +31,7 @@
         "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
 
     srcs: [
+        ":aconfigd_protos",
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 624227d..14ae3f5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -4260,6 +4260,12 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
   }
 
+  public final class TaskFragmentParentInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentParentInfo> CREATOR;
+  }
+
   public final class TaskFragmentTransaction implements android.os.Parcelable {
     ctor public TaskFragmentTransaction();
     method public void addChange(@Nullable android.window.TaskFragmentTransaction.Change);
@@ -4284,8 +4290,8 @@
     method @Nullable public android.os.IBinder getActivityToken();
     method @NonNull public android.os.Bundle getErrorBundle();
     method @Nullable public android.os.IBinder getErrorCallbackToken();
-    method @Nullable public android.content.res.Configuration getTaskConfiguration();
     method @Nullable public android.window.TaskFragmentInfo getTaskFragmentInfo();
+    method @Nullable public android.window.TaskFragmentParentInfo getTaskFragmentParentInfo();
     method @Nullable public android.os.IBinder getTaskFragmentToken();
     method public int getTaskId();
     method public int getType();
@@ -4293,7 +4299,6 @@
     method @NonNull public android.window.TaskFragmentTransaction.Change setActivityToken(@NonNull android.os.IBinder);
     method @NonNull public android.window.TaskFragmentTransaction.Change setErrorBundle(@NonNull android.os.Bundle);
     method @NonNull public android.window.TaskFragmentTransaction.Change setErrorCallbackToken(@Nullable android.os.IBinder);
-    method @NonNull public android.window.TaskFragmentTransaction.Change setTaskConfiguration(@NonNull android.content.res.Configuration);
     method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentInfo(@NonNull android.window.TaskFragmentInfo);
     method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentToken(@NonNull android.os.IBinder);
     method @NonNull public android.window.TaskFragmentTransaction.Change setTaskId(int);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 7ee3413..497d47a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2015,7 +2015,7 @@
      *     null for no callback
      * @param handler {@link Handler} identifying the callback thread,
      *     null for the main thread
-     * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
+     * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
      * succeeded.
      * @hide
      */
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5dd4a7..e1cb630 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5796,6 +5796,8 @@
      * @see #onRequestPermissionsResult
      */
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+    @SuppressLint("OnNameExpected")
+    // Suppress lint as this is an overload of the original API.
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
         final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
                 : createDeviceContext(deviceId).getPackageManager();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8b19664..0672064 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6492,8 +6492,13 @@
         // visual regressions.
         @SuppressWarnings("AndroidFrameworkCompatChange")
         private boolean bigContentViewRequired() {
-            if (!Flags.notificationExpansionOptional()
-                    && mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
+            if (Flags.notificationExpansionOptional()) {
+                // Notifications without a bigContentView, style, or actions do not need to expand
+                boolean exempt = mN.bigContentView == null
+                        && mStyle == null && mActions.size() == 0;
+                return !exempt;
+            }
+            if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
                 return true;
             }
             // Notifications with contentView and without a bigContentView, style, or actions would
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 69f29f3..4bc1ce6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10427,7 +10427,7 @@
     @WorkerThread
     public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
             Bundle settings) {
-        if (!Flags.dmrhCanSetAppRestriction()) {
+        if (!Flags.dmrhSetAppRestrictions()) {
             throwIfParentInstance("setApplicationRestrictions");
         }
 
@@ -11835,7 +11835,7 @@
     @WorkerThread
     public @NonNull Bundle getApplicationRestrictions(
             @Nullable ComponentName admin, String packageName) {
-        if (!Flags.dmrhCanSetAppRestriction()) {
+        if (!Flags.dmrhSetAppRestrictions()) {
             throwIfParentInstance("getApplicationRestrictions");
         }
 
@@ -14120,7 +14120,7 @@
     public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
         throwIfParentInstance("getParentProfileInstance");
         try {
-            if (Flags.dmrhCanSetAppRestriction()) {
+            if (Flags.dmrhSetAppRestrictions()) {
                 UserManager um = mContext.getSystemService(UserManager.class);
                 if (!um.isManagedProfile()) {
                     throw new SecurityException("The current user does not have a parent profile.");
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 3d6ec19..4154e66 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -244,10 +244,13 @@
 }
 
 flag {
-  name: "dmrh_can_set_app_restriction"
+  name: "dmrh_set_app_restrictions"
   namespace: "enterprise"
   description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
   bug: "328758346"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index cda2867..9b53461 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -33,11 +33,13 @@
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.window.ActivityWindowInfo;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.function.BiConsumer;
 
 /**
@@ -47,6 +49,8 @@
  */
 public class ClientTransactionListenerController {
 
+    private static final String TAG = "ClientTransactionListenerController";
+
     private static ClientTransactionListenerController sController;
 
     private final Object mLock = new Object();
@@ -179,10 +183,14 @@
         }
 
         // Dispatch the display changed callbacks.
-        final int displayCount = configUpdatedDisplayIds.size();
-        for (int i = 0; i < displayCount; i++) {
-            final int displayId = configUpdatedDisplayIds.valueAt(i);
-            onDisplayChanged(displayId);
+        try {
+            final int displayCount = configUpdatedDisplayIds.size();
+            for (int i = 0; i < displayCount; i++) {
+                final int displayId = configUpdatedDisplayIds.valueAt(i);
+                onDisplayChanged(displayId);
+            }
+        } catch (RejectedExecutionException e) {
+            Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
         }
     }
 
@@ -222,7 +230,11 @@
         }
 
         if (changedDisplayId != INVALID_DISPLAY) {
-            onDisplayChanged(changedDisplayId);
+            try {
+                onDisplayChanged(changedDisplayId);
+            } catch (RejectedExecutionException e) {
+                Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
+            }
         }
     }
 
@@ -235,9 +247,11 @@
     /**
      * Called when receives a {@link Configuration} changed event that is updating display-related
      * window configuration.
+     *
+     * @throws RejectedExecutionException if the display listener handler is closing.
      */
     @VisibleForTesting
-    public void onDisplayChanged(int displayId) {
+    public void onDisplayChanged(int displayId) throws RejectedExecutionException {
         mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
     }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9e316a2..c8cae82 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4353,13 +4353,6 @@
             "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
 
     /**
-     * Activity Action: Shows the contrast setting dialog.
-     * @hide
-     */
-    public static final String ACTION_SHOW_CONTRAST_DIALOG =
-            "com.android.intent.action.SHOW_CONTRAST_DIALOG";
-
-    /**
      * Broadcast Action:  A global button was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
      * caused the broadcast.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 83285e0..c506c97 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -270,11 +270,20 @@
     /**
      * Application level {@link android.content.pm.PackageManager.Property PackageManager
      * .Property} for a app to inform the installer that a file containing the app's android
-     * safety label data is bundled into the APK at the given path.
+     * safety label data is bundled into the APK as a raw resource.
+     *
+     * <p>For example:
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.content.PROPERTY_ANDROID_SAFETY_LABEL"
+     *     android:resource="@raw/app-metadata"/&gt;
+     * &lt;/application&gt;
+     * </pre>
      * @hide
      */
-    public static final String PROPERTY_ANDROID_SAFETY_LABEL_PATH =
-            "android.content.SAFETY_LABEL_PATH";
+    public static final String PROPERTY_ANDROID_SAFETY_LABEL =
+            "android.content.PROPERTY_ANDROID_SAFETY_LABEL";
 
     /**
      * A property value set within the manifest.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 83742eb..e2a131c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -247,3 +247,13 @@
     description: "Allow MAIN user to access blocked number provider"
     bug: "338579331"
 }
+
+flag {
+    name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles"
+    namespace: "profile_experiences"
+    description: "Use user states to check the state of quiet mode for managed profiles only"
+    bug: "332812630"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
index 66b0a42..3a271b4 100644
--- a/core/java/android/hardware/usb/DeviceFilter.java
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.usb.flags.Flags;
 import android.service.usb.UsbDeviceFilterProto;
 import android.util.Slog;
 
@@ -57,9 +58,12 @@
     public final String mProductName;
     // USB device serial number string (or null for unspecified)
     public final String mSerialNumber;
+    // USB interface name (or null for unspecified). This will be used when matching devices using
+    // the available interfaces.
+    public final String mInterfaceName;
 
     public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
-            String manufacturer, String product, String serialnum) {
+            String manufacturer, String product, String serialnum, String interfaceName) {
         mVendorId = vid;
         mProductId = pid;
         mClass = clasz;
@@ -68,6 +72,7 @@
         mManufacturerName = manufacturer;
         mProductName = product;
         mSerialNumber = serialnum;
+        mInterfaceName = interfaceName;
     }
 
     public DeviceFilter(UsbDevice device) {
@@ -79,6 +84,7 @@
         mManufacturerName = device.getManufacturerName();
         mProductName = device.getProductName();
         mSerialNumber = device.getSerialNumber();
+        mInterfaceName = null;
     }
 
     public DeviceFilter(@NonNull DeviceFilter filter) {
@@ -90,6 +96,7 @@
         mManufacturerName = filter.mManufacturerName;
         mProductName = filter.mProductName;
         mSerialNumber = filter.mSerialNumber;
+        mInterfaceName = filter.mInterfaceName;
     }
 
     public static DeviceFilter read(XmlPullParser parser)
@@ -102,7 +109,7 @@
         String manufacturerName = null;
         String productName = null;
         String serialNumber = null;
-
+        String interfaceName = null;
         int count = parser.getAttributeCount();
         for (int i = 0; i < count; i++) {
             String name = parser.getAttributeName(i);
@@ -114,6 +121,8 @@
                 productName = value;
             } else if ("serial-number".equals(name)) {
                 serialNumber = value;
+            }  else if ("interface-name".equals(name)) {
+                interfaceName = value;
             } else {
                 int intValue;
                 int radix = 10;
@@ -144,7 +153,7 @@
         }
         return new DeviceFilter(vendorId, productId,
                 deviceClass, deviceSubclass, deviceProtocol,
-                manufacturerName, productName, serialNumber);
+                manufacturerName, productName, serialNumber, interfaceName);
     }
 
     public void write(XmlSerializer serializer) throws IOException {
@@ -173,13 +182,25 @@
         if (mSerialNumber != null) {
             serializer.attribute(null, "serial-number", mSerialNumber);
         }
+        if (mInterfaceName != null) {
+            serializer.attribute(null, "interface-name", mInterfaceName);
+        }
         serializer.endTag(null, "usb-device");
     }
 
-    private boolean matches(int clasz, int subclass, int protocol) {
-        return ((mClass == -1 || clasz == mClass) &&
-                (mSubclass == -1 || subclass == mSubclass) &&
-                (mProtocol == -1 || protocol == mProtocol));
+    private boolean matches(int usbClass, int subclass, int protocol) {
+        return ((mClass == -1 || usbClass == mClass)
+                && (mSubclass == -1 || subclass == mSubclass)
+                && (mProtocol == -1 || protocol == mProtocol));
+    }
+
+    private boolean matches(int usbClass, int subclass, int protocol, String interfaceName) {
+        if (Flags.enableInterfaceNameDeviceFilter()) {
+            return matches(usbClass, subclass, protocol)
+                    && (mInterfaceName == null || mInterfaceName.equals(interfaceName));
+        } else {
+            return matches(usbClass, subclass, protocol);
+        }
     }
 
     public boolean matches(UsbDevice device) {
@@ -204,7 +225,7 @@
         for (int i = 0; i < count; i++) {
             UsbInterface intf = device.getInterface(i);
             if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
-                    intf.getInterfaceProtocol())) return true;
+                    intf.getInterfaceProtocol(), intf.getName())) return true;
         }
 
         return false;
@@ -320,11 +341,12 @@
 
     @Override
     public String toString() {
-        return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
-                ",mClass=" + mClass + ",mSubclass=" + mSubclass +
-                ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
-                ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
-                "]";
+        return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
+                + ",mClass=" + mClass + ",mSubclass=" + mSubclass
+                + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
+                + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber
+                + ",mInterfaceName=" + mInterfaceName
+                + "]";
     }
 
     /**
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 94df160..40e5ffb 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -16,3 +16,11 @@
     description: "Feature flag for the api to check if a port supports mode change"
     bug: "323470419"
 }
+
+flag {
+    name: "enable_interface_name_device_filter"
+    is_exported: true
+    namespace: "usb"
+    description: "Feature flag to enable interface name as a parameter for device filter"
+    bug: "312828160"
+}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 35b137a..c5b6aa7 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -129,7 +129,7 @@
         }
         ViewGroup effective = null;
         ViewParent nextParent = focused.getParent();
-        do {
+        while (nextParent instanceof ViewGroup) {
             if (nextParent == root) {
                 return effective != null ? effective : root;
             }
@@ -143,7 +143,7 @@
                 effective = vg;
             }
             nextParent = nextParent.getParent();
-        } while (nextParent instanceof ViewGroup);
+        }
         return root;
     }
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index f1cb410..d7f2b01 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1262,10 +1262,13 @@
                     mHost.getInputMethodManager(), null /* icProto */);
         }
 
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER,
+                ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
+                mHost.isHandlingPointerEvent() /* fromUser */);
         controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
                 interpolator, animationType,
                 getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
-                false /* useInsetsAnimationThread */, null /* statsToken */);
+                false /* useInsetsAnimationThread */, statsToken);
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
@@ -1567,7 +1570,9 @@
             return;
         }
         final ImeTracker.Token statsToken = runner.getStatsToken();
-        if (shown) {
+        if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+            ImeTracker.forLogging().onUserFinished(statsToken, shown);
+        } else if (shown) {
             ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
             ImeTracker.forLogging().onShown(statsToken);
@@ -1838,6 +1843,9 @@
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
             mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
             mStartingAnimation = true;
+            if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+                ImeTracker.forLogging().onDispatched(runner.getStatsToken());
+            }
             runner.setReadyDispatched(true);
             listener.onReady(runner, types);
             mStartingAnimation = false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1df851a..0715474 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -921,15 +921,6 @@
     private String mFrameRateCategoryView;
 
     /**
-     * The resolved pointer icon type requested by this window.
-     * A null value indicates the resolved pointer icon has not yet been calculated.
-     */
-    // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
-    @Nullable
-    private Integer mPointerIconType = null;
-    private PointerIcon mCustomPointerIcon = null;
-
-    /**
      * The resolved pointer icon requested by this window.
      * A null value indicates the resolved pointer icon has not yet been calculated.
      */
@@ -6430,7 +6421,6 @@
     private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
     private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
     private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
-    private static final int MSG_UPDATE_POINTER_ICON = 27;
     private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
     private static final int MSG_INSETS_CONTROL_CHANGED = 29;
     private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 30;
@@ -6495,8 +6485,6 @@
                     return "MSG_SYNTHESIZE_INPUT_EVENT";
                 case MSG_DISPATCH_WINDOW_SHOWN:
                     return "MSG_DISPATCH_WINDOW_SHOWN";
-                case MSG_UPDATE_POINTER_ICON:
-                    return "MSG_UPDATE_POINTER_ICON";
                 case MSG_POINTER_CAPTURE_CHANGED:
                     return "MSG_POINTER_CAPTURE_CHANGED";
                 case MSG_INSETS_CONTROL_CHANGED:
@@ -6747,10 +6735,6 @@
                     final int deviceId = msg.arg1;
                     handleRequestKeyboardShortcuts(receiver, deviceId);
                 } break;
-                case MSG_UPDATE_POINTER_ICON: {
-                    MotionEvent event = (MotionEvent) msg.obj;
-                    resetPointerIcon(event);
-                } break;
                 case MSG_POINTER_CAPTURE_CHANGED: {
                     final boolean hasCapture = msg.arg1 != 0;
                     handlePointerCaptureChanged(hasCapture);
@@ -7836,14 +7820,12 @@
                     || action == MotionEvent.ACTION_HOVER_EXIT) {
                 // Other apps or the window manager may change the icon type outside of
                 // this app, therefore the icon type has to be reset on enter/exit event.
-                mPointerIconType = null;
                 mResolvedPointerIcon = null;
             }
 
             if (action != MotionEvent.ACTION_HOVER_EXIT) {
                 // Resolve the pointer icon
                 if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
-                    mPointerIconType = null;
                     mResolvedPointerIcon = null;
                 }
             }
@@ -7906,12 +7888,6 @@
         return mAttachInfo.mHandlingPointerEvent;
     }
 
-    private void resetPointerIcon(MotionEvent event) {
-        mPointerIconType = null;
-        mResolvedPointerIcon = null;
-        updatePointerIcon(event);
-    }
-
 
     /**
      * If there is pointer that is showing a PointerIcon in this window, refresh the icon for that
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f454a6a..3091bf4 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -754,6 +754,19 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onDispatched */
+    static void onDispatched(@NonNull ImeTracker.Token statsToken) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onDispatched(statsToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
     @AnyThread
     @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index d992feb..edc9921 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -71,24 +71,40 @@
     /** The type of the IME request. */
     @IntDef(prefix = { "TYPE_" }, value = {
             TYPE_SHOW,
-            TYPE_HIDE
+            TYPE_HIDE,
+            TYPE_USER,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Type {}
 
-    /** IME show request type. */
+    /**
+     * IME show request type.
+     *
+     * @see android.view.InsetsController#ANIMATION_TYPE_SHOW
+     */
     int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
 
-    /** IME hide request type. */
+    /**
+     * IME hide request type.
+     *
+     * @see android.view.InsetsController#ANIMATION_TYPE_HIDE
+     */
     int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
 
+    /**
+     * IME user-controlled animation request type.
+     *
+     * @see android.view.InsetsController#ANIMATION_TYPE_USER
+     */
+    int TYPE_USER = ImeProtoEnums.TYPE_USER;
+
     /** The status of the IME request. */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_RUN,
             STATUS_CANCEL,
             STATUS_FAIL,
             STATUS_SUCCESS,
-            STATUS_TIMEOUT
+            STATUS_TIMEOUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Status {}
@@ -117,7 +133,7 @@
     @IntDef(prefix = { "ORIGIN_" }, value = {
             ORIGIN_CLIENT,
             ORIGIN_SERVER,
-            ORIGIN_IME
+            ORIGIN_IME,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Origin {}
@@ -400,20 +416,36 @@
     void onCancelled(@Nullable Token token, @Phase int phase);
 
     /**
-     * Called when the IME show request is successful.
+     * Called when the show IME request is successful.
      *
      * @param token the token tracking the current IME request or {@code null} otherwise.
      */
     void onShown(@Nullable Token token);
 
     /**
-     * Called when the IME hide request is successful.
+     * Called when the hide IME request is successful.
      *
      * @param token the token tracking the current IME request or {@code null} otherwise.
      */
     void onHidden(@Nullable Token token);
 
     /**
+     * Called when the user-controlled IME request was dispatched to the requesting app. The
+     * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     */
+    void onDispatched(@Nullable Token token);
+
+    /**
+     * Called when the animation of the user-controlled IME request finished.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param shown whether the end state of the animation was shown or hidden.
+     */
+    void onUserFinished(@Nullable Token token, boolean shown);
+
+    /**
      * Returns whether the current IME request was created due to a user interaction. This can
      * only be {@code true} when running on the view's UI thread.
      *
@@ -482,13 +514,6 @@
         /** Whether the stack trace at the request call site should be logged. */
         private boolean mLogStackTrace;
 
-        private void reloadSystemProperties() {
-            mLogProgress = SystemProperties.getBoolean(
-                    "persist.debug.imetracker", false);
-            mLogStackTrace = SystemProperties.getBoolean(
-                    "persist.debug.imerequest.logstacktrace", false);
-        }
-
         @NonNull
         @Override
         public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
@@ -497,7 +522,7 @@
             final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type,
                     origin, reason, fromUser);
 
-            Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide")
+            Log.i(TAG, token.mTag + ": " + getOnStartPrefix(type)
                     + " at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
                     + " fromUser " + fromUser,
@@ -552,6 +577,45 @@
 
             Log.i(TAG, token.mTag + ": onHidden");
         }
+
+        @Override
+        public void onDispatched(@Nullable Token token) {
+            if (token == null) return;
+            IInputMethodManagerGlobalInvoker.onDispatched(token);
+
+            Log.i(TAG, token.mTag + ": onDispatched");
+        }
+
+        @Override
+        public void onUserFinished(@Nullable Token token, boolean shown) {
+            if (token == null) return;
+            // This is already sent to ImeTrackerService to mark it finished during onDispatched.
+
+            Log.i(TAG, token.mTag + ": onUserFinished " + (shown ? "shown" : "hidden"));
+        }
+
+        /**
+         * Gets the prefix string for {@link #onStart} based on the given request type.
+         *
+         * @param type request type for which to create the prefix string with.
+         */
+        @NonNull
+        private static String getOnStartPrefix(@Type int type) {
+            return switch (type) {
+                case TYPE_SHOW -> "onRequestShow";
+                case TYPE_HIDE -> "onRequestHide";
+                case TYPE_USER -> "onRequestUser";
+                default -> "onRequestUnknown";
+            };
+        }
+
+        /** Reloads the system properties related to this class. */
+        private void reloadSystemProperties() {
+            mLogProgress = SystemProperties.getBoolean(
+                    "persist.debug.imetracker", false);
+            mLogStackTrace = SystemProperties.getBoolean(
+                    "persist.debug.imerequest.logstacktrace", false);
+        }
     };
 
     /** The singleton IME tracker instance for instrumenting jank metrics. */
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index a77c234..1555416 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.os.Parcel;
@@ -27,10 +29,13 @@
 import java.util.Objects;
 
 /**
- * The information about the parent Task of a particular TaskFragment
+ * The information about the parent Task of a particular TaskFragment.
+ *
  * @hide
  */
-public class TaskFragmentParentInfo implements Parcelable {
+@SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+@TestApi
+public final class TaskFragmentParentInfo implements Parcelable {
     @NonNull
     private final Configuration mConfiguration = new Configuration();
 
@@ -42,6 +47,7 @@
 
     @Nullable private final SurfaceControl mDecorSurface;
 
+    /** @hide */
     public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
             boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) {
         mConfiguration.setTo(configuration);
@@ -51,6 +57,7 @@
         mDecorSurface = decorSurface;
     }
 
+    /** @hide */
     public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
         mConfiguration.setTo(info.getConfiguration());
         mDisplayId = info.mDisplayId;
@@ -59,7 +66,11 @@
         mDecorSurface = info.mDecorSurface;
     }
 
-    /** The {@link Configuration} of the parent Task */
+    /**
+     * The {@link Configuration} of the parent Task
+     *
+     * @hide
+     */
     @NonNull
     public Configuration getConfiguration() {
         return mConfiguration;
@@ -68,19 +79,27 @@
     /**
      * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
      * Task is detached from previously associated display.
+     *
+     * @hide
      */
     public int getDisplayId() {
         return mDisplayId;
     }
 
-    /** Whether the parent Task is visible or not */
+    /**
+     * Whether the parent Task is visible or not
+     *
+     * @hide
+     */
     public boolean isVisible() {
         return mVisible;
     }
 
     /**
      * Whether the parent Task has any direct child activity, which is not embedded in any
-     * TaskFragment, or not
+     * TaskFragment, or not.
+     *
+     * @hide
      */
     public boolean hasDirectActivity() {
         return mHasDirectActivity;
@@ -93,6 +112,8 @@
      * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
      * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
      * be dispatched to the client.
+     *
+     * @hide
      */
     public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
         if (that == null) {
@@ -103,6 +124,7 @@
                 && mDecorSurface == that.mDecorSurface;
     }
 
+    /** @hide */
     @Nullable
     public SurfaceControl getDecorSurface() {
         return mDecorSurface;
@@ -156,6 +178,7 @@
         return result;
     }
 
+    @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         mConfiguration.writeToParcel(dest, flags);
@@ -173,6 +196,8 @@
         mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR);
     }
 
+    @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+    @NonNull
     public static final Creator<TaskFragmentParentInfo> CREATOR =
             new Creator<TaskFragmentParentInfo>() {
                 @Override
@@ -186,6 +211,7 @@
                 }
             };
 
+    @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 4dada10..32e3f5a 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -24,7 +24,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -248,13 +247,6 @@
             return this;
         }
 
-        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
-        /** Configuration of the parent Task. */
-        @NonNull
-        public Change setTaskConfiguration(@NonNull Configuration configuration) {
-            return this;
-        }
-
         /**
          * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction}
          * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to
@@ -299,12 +291,11 @@
             return this;
         }
 
-        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
         /**
          * Sets info of the parent Task of the embedded TaskFragment.
          * @see TaskFragmentParentInfo
          *
-         * @hide pending unhide
+         * @hide
          */
         @NonNull
         public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
@@ -338,12 +329,6 @@
             return mTaskId;
         }
 
-        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
-        @Nullable
-        public Configuration getTaskConfiguration() {
-            return mTaskFragmentParentInfo.getConfiguration();
-        }
-
         @Nullable
         public IBinder getErrorCallbackToken() {
             return mErrorCallbackToken;
@@ -365,8 +350,10 @@
             return mActivityToken;
         }
 
-        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
-        /** @hide pending unhide */
+        /**
+         * Obtains the {@link TaskFragmentParentInfo} for this transaction.
+         */
+        @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
         @Nullable
         public TaskFragmentParentInfo getTaskFragmentParentInfo() {
             return mTaskFragmentParentInfo;
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 cd13c4a..4b2beb9 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
@@ -9,6 +9,16 @@
 }
 
 flag {
+  name: "disable_thin_letterboxing_policy"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether reachability is disabled in case of thin letterboxing"
+  bug: "341027847"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
   namespace: "large_screen_experiences_app_compat"
   description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index faaf7d5..8132652 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -24,6 +24,8 @@
 import android.content.pm.PackagePartitions.SystemPartition;
 import android.os.Build;
 import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Xml;
@@ -241,6 +243,18 @@
         }
     }
 
+    @FunctionalInterface
+    public interface SysPropWrapper{
+        /**
+         * Get system property
+         *
+         * @param property the key to look up.
+         *
+         * @return The property value if found, empty string otherwise.
+         */
+        String get(String property);
+    }
+
     /**
      * Retrieves overlays configured within the partition in increasing priority order.
      *
@@ -320,6 +334,76 @@
     }
 
     /**
+     * Expand the property inside a rro configuration path.
+     *
+     * A RRO configuration can contain a property, this method expands
+     * the property to its value.
+     *
+     * Only read only properties allowed, prefixed with ro. Other
+     * properties will raise exception.
+     *
+     * Only a single property in the path is allowed.
+     *
+     * Example "${ro.boot.hardware.sku}/config.xml" would expand to
+     *     "G020N/config.xml"
+     *
+     * @param configPath path to expand
+     * @param sysPropWrapper method used for reading properties
+     *
+     * @return The expanded path. Returns null if configPath is null.
+     */
+    @VisibleForTesting
+    public static String expandProperty(String configPath,
+            SysPropWrapper sysPropWrapper) {
+        if (configPath == null) {
+            return null;
+        }
+
+        int propStartPos = configPath.indexOf("${");
+        if (propStartPos == -1) {
+            // No properties inside the string, return as is
+            return configPath;
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(configPath.substring(0, propStartPos));
+
+        // Read out the end position
+        int propEndPos = configPath.indexOf("}", propStartPos);
+        if (propEndPos == -1) {
+            throw new IllegalStateException("Malformed property, unmatched braces, in: "
+                    + configPath);
+        }
+
+        // Confirm that there is only one property inside the string
+        if (configPath.indexOf("${", propStartPos + 2) != -1) {
+            throw new IllegalStateException("Only a single property supported in path: "
+                    + configPath);
+        }
+
+        final String propertyName = configPath.substring(propStartPos + 2, propEndPos);
+        if (!propertyName.startsWith("ro.")) {
+            throw new IllegalStateException("Only read only properties can be used when "
+                    + "merging RRO config files: " + propertyName);
+        }
+        final String propertyValue = sysPropWrapper.get(propertyName);
+        if (TextUtils.isEmpty(propertyValue)) {
+            throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName);
+        }
+        Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName));
+        sb.append(propertyValue);
+
+        // propEndPos points to '}', need to step to next character, might be outside of string
+        propEndPos = propEndPos + 1;
+        // Append the remainder, if exists
+        if (propEndPos < configPath.length()) {
+            sb.append(configPath.substring(propEndPos));
+        }
+
+        return sb.toString();
+    }
+
+    /**
      * Parses a <merge> tag within an overlay configuration file.
      *
      * Merge tags allow for other configuration files to be "merged" at the current parsing
@@ -331,10 +415,21 @@
             @Nullable OverlayScanner scanner,
             @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
             @NonNull ParsingContext parsingContext) {
-        final String path = parser.getAttributeValue(null, "path");
+        final String path;
+
+        try {
+            SysPropWrapper sysPropWrapper = p -> {
+                return SystemProperties.get(p, "");
+            };
+            path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper);
+        } catch (IllegalStateException e) {
+            throw new IllegalStateException(String.format("<merge> path expand error in %s at %s",
+                    configFile, parser.getPositionDescription()), e);
+        }
+
         if (path == null) {
-            throw new IllegalStateException(String.format("<merge> without path in %s at %s"
-                    + configFile, parser.getPositionDescription()));
+            throw new IllegalStateException(String.format("<merge> without path in %s at %s",
+                    configFile, parser.getPositionDescription()));
         }
 
         if (path.startsWith("/")) {
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index ab4edb6..ebae39e 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -64,20 +64,28 @@
     oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
 
     /**
-     * Called when the IME show request is successful.
+     * Called when the show IME request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
     oneway void onShown(in ImeTracker.Token statsToken);
 
     /**
-     * Called when the IME hide request is successful.
+     * Called when the hide IME request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
     oneway void onHidden(in ImeTracker.Token statsToken);
 
     /**
+     * Called when the user-controlled IME request was dispatched to the requesting app. The
+     * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+     *
+     * @param statsToken the token tracking the current IME request.
+     */
+    oneway void onDispatched(in ImeTracker.Token statsToken);
+
+    /**
      * Checks whether there are any pending IME visibility requests.
      *
      * @return {@code true} iff there are pending IME visibility requests.
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index a0aad31..2a5593f 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -297,6 +297,8 @@
                 return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT";
             case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION:
                 return "SHOW_SOFT_INPUT_IMM_DEPRECATION";
+            case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION:
+                return "CONTROL_WINDOW_INSETS_ANIMATION";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index da738a0..eb6a810 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -88,6 +88,7 @@
         SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
+        SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
 })
 public @interface SoftInputShowHideReason {
     /** Default, undefined reason. */
@@ -397,4 +398,10 @@
      * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}.
      */
     int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION;
+
+    /**
+     * Show / Hide soft input by application-controlled animation in
+     * {@link android.view.InsetsController#controlWindowInsetsAnimation}.
+     */
+    int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION;
 }
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8d97362..80cf088 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -27,16 +27,18 @@
     <!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
     <bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
 
-    <!-- CPU power stats collection throttle period in milliseconds.  Since power stats collection
-    is a relatively expensive operation, this throttle period may need to be adjusted for low-power
-    devices-->
-    <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+    <!-- Power stats collection throttle periods in milliseconds.  Since power stats collection
+    is a relatively expensive operation, these throttle period may need to be adjusted for low-power
+    devices.
 
-    <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
-    <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer>
-
-    <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
-    <integer name="config_defaultPowerStatsThrottlePeriodWifi">3600000</integer>
+    The syntax of this config string is as follows:
+    <pre>
+       power-component-name1:throttle-period-millis1 power-component-name2:throttle-period-ms2 ...
+    </pre>
+    Use "*" for the power-component-name to represent the default for all power components
+    not mentioned by name.
+    -->
+    <string name="config_powerStatsThrottlePeriods">cpu:60000 *:300000</string>
 
     <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
     stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a9d03f5..acd3b37 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5243,9 +5243,7 @@
 
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
-  <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
-  <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" />
-  <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodWifi" />
+  <java-symbol type="string" name="config_powerStatsThrottlePeriods" />
   <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
   <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index b6f4429..ee1d1e1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -65,6 +66,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.function.BiConsumer;
 
 /**
@@ -221,4 +223,17 @@
                 123 /* newDisplayId */, true /* shouldReportConfigChange*/);
         inOrder.verify(mController).onContextConfigurationPostChanged(context);
     }
+
+    @Test
+    public void testDisplayListenerHandlerClosed() {
+        doReturn(123).when(mActivity).getDisplayId();
+        doThrow(new RejectedExecutionException()).when(mController).onDisplayChanged(123);
+
+        mController.onContextConfigurationPreChanged(mActivity);
+        mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200));
+        mController.onContextConfigurationPostChanged(mActivity);
+
+        // No crash
+        verify(mController).onDisplayChanged(123);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
new file mode 100644
index 0000000..4eccbe5
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.content.res;
+
+import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.content.om.OverlayConfigParser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class OverlayConfigParserTest {
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropNotRoProp() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("${persist.value}/path", sysProp);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropMissingEndBracket() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("${ro.value/path", sysProp);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMergeOnlyPropStart() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("path/${", sysProp);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropInProp() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp);
+    }
+
+    /**
+     * The path is only allowed to contain one property.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropMultipleProps() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp);
+    }
+
+    @Test
+    public void testMergePropOneProp() {
+        final SysPropWrapper sysProp = p -> {
+            if ("ro.value".equals(p)) {
+                return "dummy_value";
+            } else {
+                return "invalid";
+            }
+        };
+
+        // Property in the beginnig of the string
+        String result = OverlayConfigParser.expandProperty("${ro.value}/path",
+                sysProp);
+        assertEquals("dummy_value/path", result);
+
+        // Property in the middle of the string
+        result = OverlayConfigParser.expandProperty("path/${ro.value}/file",
+                sysProp);
+        assertEquals("path/dummy_value/file", result);
+
+        // Property at the of the string
+        result = OverlayConfigParser.expandProperty("path/${ro.value}",
+                sysProp);
+        assertEquals("path/dummy_value", result);
+
+        // Property is the entire string
+        result = OverlayConfigParser.expandProperty("${ro.value}",
+                sysProp);
+        assertEquals("dummy_value", result);
+    }
+
+    @Test
+    public void testMergePropNoProp() {
+        final SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+
+        final String path = "no_props/path";
+        String result = OverlayConfigParser.expandProperty(path, sysProp);
+        assertEquals(path, result);
+    }
+}
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 000f6ef..b41a607 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 01deb49..b93cd46 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -583,6 +583,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "7211222997110112110": {
+      "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/ActivityRefresher.java"
+    },
     "1665699123574159131": {
       "message": "Starting activity when config will change = %b",
       "level": "VERBOSE",
@@ -1771,12 +1777,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
     },
-    "-7756685416834187936": {
-      "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
-    },
     "-5176775281239247368": {
       "message": "Reverting orientation after camera compat force rotation",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 9284c0e..4bc3ece 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -2493,7 +2493,11 @@
             return mNativePtr;
         }
 
-        /** Need a nested class due to b/337329128. */
+        /**
+         * These methods can't be put in the Rgb class directly, because ColorSpace's
+         * static initializer instantiates Rgb, whose constructor needs them, which is a variation
+         * of b/337329128.
+         */
         static class Native {
             static native long nativeGetNativeFinalizer();
             static native long nativeCreate(float a, float b, float c, float d,
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 dcd4062..785e30d 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,8 +69,12 @@
 
     /** Returns {@code true} if the transition is opening or closing mode. */
     public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
-        return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
-                || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
+        return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+    }
+
+    /** Returns {@code true} if the transition is opening mode. */
+    public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) {
+        return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT;
     }
 
     /** Returns {@code true} if the transition has a display change. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9e6c5fb..d8c1cab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1170,7 +1170,9 @@
      * @param bubbleKey key of the bubble being dragged
      */
     public void startBubbleDrag(String bubbleKey) {
-        onBubbleDrag(bubbleKey, true /* isBeingDragged */);
+        if (mBubbleData.getSelectedBubble() != null) {
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+        }
         if (mBubbleStateListener != null) {
             boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
             Rect rect = new Rect();
@@ -1183,23 +1185,29 @@
     }
 
     /**
-     * A bubble is no longer being dragged in Launcher. As was released in given location.
+     * A bubble is no longer being dragged in Launcher. And was released in given location.
      * Will be called only when bubble bar is expanded.
      *
-     * @param bubbleKey key of the bubble being dragged
      * @param location  location where bubble was released
      */
-    public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
+    public void stopBubbleDrag(BubbleBarLocation location) {
         mBubblePositioner.setBubbleBarLocation(location);
-        onBubbleDrag(bubbleKey, false /* isBeingDragged */);
+        if (mBubbleData.getSelectedBubble() != null) {
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+        }
     }
 
-    private void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
-        // TODO(b/330585402): collapse stack if any bubble is dragged
-        if (mBubbleData.getSelectedBubble() != null
-                && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
-            // Should collapse/expand only if equals to selected bubble.
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+    /**
+     * A bubble was dragged and is released in dismiss target in Launcher.
+     *
+     * @param bubbleKey key of the bubble being dragged to dismiss target
+     */
+    public void dragBubbleToDismiss(String bubbleKey) {
+        String selectedBubbleKey = mBubbleData.getSelectedBubbleKey();
+        removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE);
+        if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
+            // We did not remove the selected bubble. Expand it again
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
         }
     }
 
@@ -2358,12 +2366,6 @@
         }
 
         @Override
-        public void removeBubble(String key) {
-            mMainExecutor.execute(
-                    () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
-        }
-
-        @Override
         public void removeAllBubbles() {
             mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
         }
@@ -2379,8 +2381,13 @@
         }
 
         @Override
-        public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
-            mMainExecutor.execute(() -> mController.stopBubbleDrag(bubbleKey, location));
+        public void stopBubbleDrag(BubbleBarLocation location) {
+            mMainExecutor.execute(() -> mController.stopBubbleDrag(location));
+        }
+
+        @Override
+        public void dragBubbleToDismiss(String key) {
+            mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key));
         }
 
         @Override
@@ -2397,7 +2404,10 @@
 
         @Override
         public void setBubbleBarBounds(Rect bubbleBarBounds) {
-            mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds));
+            mMainExecutor.execute(() -> {
+                mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
+                if (mLayerView != null) mLayerView.updateExpandedView();
+            });
         }
     }
 
@@ -2709,6 +2719,15 @@
                     () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
                             sensitiveNotificationProtectionActive));
         }
+
+        @Override
+        public boolean canShowBubbleNotification() {
+            // in bubble bar mode, when the IME is visible we can't animate new bubbles.
+            if (BubbleController.this.isShowingAsBubbleBar()) {
+                return !BubbleController.this.mBubblePositioner.getIsImeVisible();
+            }
+            return true;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index ea30af5..26483c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -327,6 +327,14 @@
         return mSelectedBubble;
     }
 
+    /**
+     * Returns the key of the selected bubble, or null if no bubble is selected.
+     */
+    @Nullable
+    public String getSelectedBubbleKey() {
+        return mSelectedBubble != null ? mSelectedBubble.getKey() : null;
+    }
+
     public BubbleOverflow getOverflow() {
         return mOverflow;
     }
@@ -1228,9 +1236,7 @@
     public void dump(PrintWriter pw) {
         pw.println("BubbleData state:");
         pw.print("  selected: ");
-        pw.println(mSelectedBubble != null
-                ? mSelectedBubble.getKey()
-                : "null");
+        pw.println(getSelectedBubbleKey());
         pw.print("  expanded: ");
         pw.println(mExpanded);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c4bbe32..a35a004 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -324,6 +324,11 @@
         return 0;
     }
 
+    /** Returns whether the IME is visible. */
+    public boolean getIsImeVisible() {
+        return mImeVisible;
+    }
+
     /** Sets whether the IME is visible. **/
     public void setImeVisible(boolean visible, int height) {
         mImeVisible = visible;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 322088b..1d053f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -297,6 +297,15 @@
             boolean sensitiveNotificationProtectionActive);
 
     /**
+     * Determines whether Bubbles can show notifications.
+     *
+     * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble
+     * notification is suppressed and should be shown by the Notifications pipeline as regular
+     * notifications.
+     */
+    boolean canShowBubbleNotification();
+
+    /**
      * A listener to be notified of bubble state changes, used by launcher to render bubbles in
      * its process.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 66f77fa..1eff149 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -33,7 +33,7 @@
 
     oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
 
-    oneway void removeBubble(in String key) = 4;
+    oneway void dragBubbleToDismiss(in String key) = 4;
 
     oneway void removeAllBubbles() = 5;
 
@@ -47,5 +47,5 @@
 
     oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
 
-    oneway void stopBubbleDrag(in String key, in BubbleBarLocation location) = 11;
+    oneway void stopBubbleDrag(in BubbleBarLocation location) = 11;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index a351cef..123cc7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -356,7 +356,7 @@
     }
 
     /** Updates the expanded view size and position. */
-    private void updateExpandedView() {
+    public void updateExpandedView() {
         if (mExpandedView == null || mExpandedBubble == null) return;
         boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 414a9d1..01364d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -133,6 +133,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
             @NonNull PipTransitionState pipTransitionState,
+            @NonNull PipScheduler pipScheduler,
             @NonNull SizeSpecSource sizeSpecSource,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -140,7 +141,7 @@
             @ShellMainThread ShellExecutor mainExecutor,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
         return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper,
+                pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
                 floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                 pipPerfHintControllerOptional);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e885262..e1657f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -854,7 +854,8 @@
         mPipUiEventLoggerLogger.log(uiEventEnum);
 
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
+                "onTaskAppeared: %s, state=%s, taskId=%s", mTaskInfo.topActivity,
+                mPipTransitionState, mTaskInfo.taskId);
         if (mPipTransitionState.getInSwipePipToHomeTransition()) {
             if (!mWaitForFixedRotation) {
                 onEndOfSwipePipToHomeTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index a097a0f..be10151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -58,7 +58,8 @@
  * A helper to animate and manipulate the PiP.
  */
 public class PipMotionHelper implements PipAppOpsListener.Callback,
-        FloatingContentCoordinator.FloatingContent {
+        FloatingContentCoordinator.FloatingContent,
+        PipTransitionState.PipTransitionStateChangedListener {
     private static final String TAG = "PipMotionHelper";
     private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
     private static final boolean DEBUG = false;
@@ -181,7 +182,7 @@
             }
         };
         mPipTransitionState = pipTransitionState;
-        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
     }
 
     void init() {
@@ -687,7 +688,8 @@
         // setAnimatingToBounds(toBounds);
     }
 
-    private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
             @PipTransitionState.TransitionState int newState,
             @Nullable Bundle extra) {
         switch (newState) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 04cf350..b55a41d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -24,6 +24,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.os.Bundle;
 import android.os.Looper;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -32,6 +33,7 @@
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.VisibleForTesting;
@@ -51,16 +53,20 @@
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
  * trigger dynamic resize.
  */
-public class PipResizeGestureHandler {
+public class PipResizeGestureHandler implements
+        PipTransitionState.PipTransitionStateChangedListener {
 
     private static final String TAG = "PipResizeGestureHandler";
     private static final int PINCH_RESIZE_SNAP_DURATION = 250;
     private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
+    private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change";
 
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final PipBoundsState mPipBoundsState;
     private final PipTouchState mPipTouchState;
+    private final PipScheduler mPipScheduler;
+    private final PipTransitionState mPipTransitionState;
     private final PhonePipMenuController mPhonePipMenuController;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
@@ -88,6 +94,7 @@
     private boolean mIsSysUiStateValid;
     private boolean mThresholdCrossed;
     private boolean mOngoingPinchToResize = false;
+    private boolean mWaitingForBoundsChangeTransition = false;
     private float mAngle = 0;
     int mFirstIndex = -1;
     int mSecondIndex = -1;
@@ -104,11 +111,17 @@
     private int mCtrlType;
     private int mOhmOffset;
 
-    public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipTouchState pipTouchState,
+    public PipResizeGestureHandler(Context context,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState,
+            PipTouchState pipTouchState,
+            PipScheduler pipScheduler,
+            PipTransitionState pipTransitionState,
             Runnable updateMovementBoundsRunnable,
-            PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
-            ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
+            PipUiEventLogger pipUiEventLogger,
+            PhonePipMenuController menuActivityController,
+            ShellExecutor mainExecutor,
+            @Nullable PipPerfHintController pipPerfHintController) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = mainExecutor;
@@ -116,6 +129,11 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mPipTouchState = pipTouchState;
+        mPipScheduler = pipScheduler;
+
+        mPipTransitionState = pipTransitionState;
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
+
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -125,6 +143,7 @@
             mUserResizeBounds.set(rect);
             // mMotionHelper.synchronizePinnedStackBounds();
             mUpdateMovementBoundsRunnable.run();
+            mPipBoundsState.setBounds(rect);
             resetState();
         };
     }
@@ -202,7 +221,7 @@
     @VisibleForTesting
     void onInputEvent(InputEvent ev) {
         if (!mEnablePinchResize) {
-            // No need to handle anything if neither form of resizing is enabled.
+            // No need to handle anything if resizing isn't enabled.
             return;
         }
 
@@ -227,7 +246,7 @@
                 }
             }
 
-            if (mEnablePinchResize && mOngoingPinchToResize) {
+            if (mOngoingPinchToResize) {
                 onPinchResize(mv);
             }
         }
@@ -249,13 +268,11 @@
     }
 
     boolean willStartResizeGesture(MotionEvent ev) {
-        if (isInValidSysUiState()) {
-            if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                if (mEnablePinchResize && ev.getPointerCount() == 2) {
-                    onPinchResize(ev);
-                    mOngoingPinchToResize = mAllowGesture;
-                    return mAllowGesture;
-                }
+        if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+            if (mEnablePinchResize && ev.getPointerCount() == 2) {
+                onPinchResize(ev);
+                mOngoingPinchToResize = mAllowGesture;
+                return mAllowGesture;
             }
         }
         return false;
@@ -284,7 +301,6 @@
             mSecondIndex = -1;
             mAllowGesture = false;
             finishResize();
-            cleanUpHighPerfSessionMaybe();
         }
 
         if (ev.getPointerCount() != 2) {
@@ -347,10 +363,7 @@
                         mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
                         mDownBounds, mLastResizeBounds);
 
-                /*
-                mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
-                        mAngle, null);
-                 */
+                mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
                 mPipBoundsState.setHasUserResizedPip(true);
             }
         }
@@ -399,57 +412,43 @@
     }
 
     private void finishResize() {
-        if (!mLastResizeBounds.isEmpty()) {
-            // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
-            // position correctly. Drag-resize does not need to move, so just finalize resize.
-            if (mOngoingPinchToResize) {
-                final Rect startBounds = new Rect(mLastResizeBounds);
-                // If user resize is pretty close to max size, just auto resize to max.
-                if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
-                        || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
-                    resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
-                }
-
-                // If user resize is smaller than min size, auto resize to min
-                if (mLastResizeBounds.width() < mMinSize.x
-                        || mLastResizeBounds.height() < mMinSize.y) {
-                    resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
-                }
-
-                // get the current movement bounds
-                final Rect movementBounds = mPipBoundsAlgorithm
-                        .getMovementBounds(mLastResizeBounds);
-
-                // snap mLastResizeBounds to the correct edge based on movement bounds
-                snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
-
-                final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
-                        mLastResizeBounds, movementBounds);
-                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
-
-                // disable any touch events beyond resizing too
-                mPipTouchState.setAllowInputEvents(false);
-
-                /*
-                mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
-                        PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
-                            // enable touch events
-                            mPipTouchState.setAllowInputEvents(true);
-                        });
-                 */
-            } else {
-                /*
-                mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
-                        TRANSITION_DIRECTION_USER_RESIZE,
-                        mUpdateResizeBoundsCallback);
-                 */
-            }
-            final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
-            mPipUiEventLogger.log(
-                    PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
-        } else {
+        if (mLastResizeBounds.isEmpty()) {
             resetState();
         }
+        if (!mOngoingPinchToResize) {
+            return;
+        }
+        final Rect startBounds = new Rect(mLastResizeBounds);
+
+        // If user resize is pretty close to max size, just auto resize to max.
+        if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+                || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+            resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+        }
+
+        // If user resize is smaller than min size, auto resize to min
+        if (mLastResizeBounds.width() < mMinSize.x
+                || mLastResizeBounds.height() < mMinSize.y) {
+            resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+        }
+
+        // get the current movement bounds
+        final Rect movementBounds = mPipBoundsAlgorithm
+                .getMovementBounds(mLastResizeBounds);
+
+        // snap mLastResizeBounds to the correct edge based on movement bounds
+        snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
+        final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+                mLastResizeBounds, movementBounds);
+        mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+        // Update the transition state to schedule a resize transition.
+        Bundle extra = new Bundle();
+        extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+
+        mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
     }
 
     private void resetState() {
@@ -509,6 +508,40 @@
         rect.set(l, t, r, b);
     }
 
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+        switch (newState) {
+            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+                if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
+                mWaitingForBoundsChangeTransition = true;
+                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
+                break;
+            case PipTransitionState.CHANGING_PIP_BOUNDS:
+                if (!mWaitingForBoundsChangeTransition) break;
+
+                // If bounds change transition was scheduled from this class, handle leash updates.
+                mWaitingForBoundsChangeTransition = false;
+
+                SurfaceControl.Transaction startTx = extra.getParcelable(
+                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+                Rect destinationBounds = extra.getParcelable(
+                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+                        destinationBounds.left, destinationBounds.top);
+                startTx.apply();
+
+                // All motion operations have actually finished, so make bounds cache updates.
+                cleanUpHighPerfSessionMaybe();
+
+                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
+                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+
+                mUpdateResizeBoundsCallback.accept(destinationBounds);
+                break;
+        }
+    }
+
     /**
      * Dumps the {@link PipResizeGestureHandler} state.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index c5b0de3..4947507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
@@ -165,6 +166,16 @@
      * {@link WindowContainerTransaction}.
      */
     public void scheduleUserResizePip(Rect toBounds) {
+        scheduleUserResizePip(toBounds, 0f /* degrees */);
+    }
+
+    /**
+     * Directly perform a scaled matrix transformation on the leash. This will not perform any
+     * {@link WindowContainerTransaction}.
+     *
+     * @param degrees the angle to rotate the bounds to.
+     */
+    public void scheduleUserResizePip(Rect toBounds, float degrees) {
         if (toBounds.isEmpty()) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
@@ -172,7 +183,16 @@
         }
         SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        tx.setPosition(leash, toBounds.left, toBounds.top);
+
+        Matrix transformTensor = new Matrix();
+        final float[] mMatrixTmp = new float[9];
+        final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width();
+
+        transformTensor.setScale(scale, scale);
+        transformTensor.postTranslate(toBounds.left, toBounds.top);
+        transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());
+
+        tx.setMatrix(leash, transformTensor, mMatrixTmp);
         tx.apply();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 9c6e3ea..319d199 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -73,7 +73,7 @@
  * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
  * the PIP.
  */
-public class PipTouchHandler {
+public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener {
 
     private static final String TAG = "PipTouchHandler";
     private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
@@ -84,6 +84,7 @@
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     @NonNull private final PipBoundsState mPipBoundsState;
     @NonNull private final PipTransitionState mPipTransitionState;
+    @NonNull private final PipScheduler mPipScheduler;
     @NonNull private final SizeSpecSource mSizeSpecSource;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -173,6 +174,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
             @NonNull PipTransitionState pipTransitionState,
+            @NonNull PipScheduler pipScheduler,
             @NonNull SizeSpecSource sizeSpecSource,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -188,6 +190,7 @@
 
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+        mPipScheduler = pipScheduler;
         mSizeSpecSource = sizeSpecSource;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -213,10 +216,10 @@
                 },
                 menuController::hideMenu,
                 mainExecutor);
-        mPipResizeGestureHandler =
-                new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
-                        mTouchState, this::updateMovementBounds, pipUiEventLogger,
-                        menuController, mainExecutor, mPipPerfHintController);
+        mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
+                pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
+                this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+                mPipPerfHintController);
         mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
 
         if (PipUtils.isPip2ExperimentEnabled()) {
@@ -1075,7 +1078,8 @@
         mPipResizeGestureHandler.setOhmOffset(offset);
     }
 
-    private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
             @PipTransitionState.TransitionState int newState,
             @Nullable Bundle extra) {
         switch (newState) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index b10176d..8e97068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -47,6 +47,7 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -67,6 +68,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
+import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -2836,7 +2838,7 @@
             mSplitLayout.setFreezeDividerWindow(false);
             final StageChangeRecord record = new StageChangeRecord();
             final int transitType = info.getType();
-            boolean hasEnteringPip = false;
+            TransitionInfo.Change pipChange = null;
             for (int iC = 0; iC < info.getChanges().size(); ++iC) {
                 final TransitionInfo.Change change = info.getChanges().get(iC);
                 if (change.getMode() == TRANSIT_CHANGE
@@ -2847,7 +2849,7 @@
                 }
 
                 if (mMixedHandler.isEnteringPip(change, transitType)) {
-                    hasEnteringPip = true;
+                    pipChange = change;
                 }
 
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2899,9 +2901,19 @@
                 }
             }
 
-            if (hasEnteringPip) {
+            if (pipChange != null) {
+                TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange,
+                        mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId,
+                        getSplitItemStage(pipChange.getLastParent()));
+                if (pipReplacingChange != null) {
+                    // Set an enter transition for when startAnimation gets called again
+                    mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
+                            TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+                }
+
                 mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
-                        startTransaction, finishTransaction, finishCallback);
+                        startTransaction, finishTransaction, finishCallback,
+                        pipReplacingChange != null);
                 notifySplitAnimationFinished();
                 return true;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1e305c5..f77c80d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -177,9 +177,11 @@
     @Override
     @CallSuper
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d "
+                        + "taskActivity=%s",
                 taskInfo.taskId, taskInfo.parentTaskId,
-                mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
+                mRootTaskInfo != null ? mRootTaskInfo.taskId : -1,
+                taskInfo.baseActivity);
         if (mRootTaskInfo == null) {
             mRootLeash = leash;
             mRootTaskInfo = taskInfo;
@@ -213,6 +215,8 @@
     @Override
     @CallSuper
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s",
+                taskInfo.taskId, taskInfo.baseActivity);
         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
             // Inflates split decor view only when the root task is visible.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 968b27b..bcacecb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -77,6 +77,7 @@
     private ActivityEmbeddingController mActivityEmbeddingController;
 
     abstract static class MixedTransition {
+        /** Entering Pip from split, breaks split. */
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
 
         /** Both the display and split-state (enter/exit) is changing */
@@ -103,6 +104,9 @@
         /** Enter pip from one of the Activity Embedding windows. */
         static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9;
 
+        /** Entering Pip from split, but replace the Pip stage instead of breaking split. */
+        static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
@@ -484,9 +488,11 @@
     // TODO(b/287704263): Remove when split/mixed are reversed.
     public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            Transitions.TransitionFinishCallback finishCallback) {
-        final MixedTransition mixed = createDefaultMixedTransition(
-                MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
+            Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) {
+        int type = replacingPip
+                ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT
+                : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT;
+        final MixedTransition mixed = createDefaultMixedTransition(type, transition);
         mActiveTransitions.add(mixed);
         Transitions.TransitionFinishCallback callback = wct -> {
             mActiveTransitions.remove(mixed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index b028bd6..0ada749 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -76,7 +76,12 @@
                             info, startTransaction, finishTransaction, finishCallback);
             case TYPE_ENTER_PIP_FROM_SPLIT ->
                     animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
-                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+                            /*replacingPip*/ false);
+            case TYPE_ENTER_PIP_REPLACE_FROM_SPLIT ->
+                    animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+                            /*replacingPip*/ true);
             case TYPE_KEYGUARD ->
                     animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
                             mKeyguardHandler, mPipHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index ffc0b76..e8b01b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -23,11 +23,15 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
@@ -45,7 +49,8 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler,
-            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler,
+            boolean replacingPip) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                 + "entering PIP while Split-Screen is foreground.");
         TransitionInfo.Change pipChange = null;
@@ -99,7 +104,7 @@
             // we need a separate one to send over to launcher.
             SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
             @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
-            if (splitHandler.isSplitScreenVisible()) {
+            if (splitHandler.isSplitScreenVisible() && !replacingPip) {
                 // The non-going home case, we could be pip-ing one of the split stages and keep
                 // showing the other
                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -115,11 +120,12 @@
                         break;
                     }
                 }
+
+                // Let split update internal state for dismiss.
+                splitHandler.prepareDismissAnimation(topStageToKeep,
+                        EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+                        finishTransaction);
             }
-            // Let split update internal state for dismiss.
-            splitHandler.prepareDismissAnimation(topStageToKeep,
-                    EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
-                    finishTransaction);
 
             // We are trying to accommodate launcher's close animation which can't handle the
             // divider-bar, so if split-handler is closing the divider-bar, just hide it and
@@ -152,6 +158,44 @@
         return true;
     }
 
+    /**
+     * Check to see if we're only closing split to enter pip or if we're replacing pip with
+     * another task. If we are replacing, this will return the change for the task we are replacing
+     * pip with
+     *
+     * @param info Any number of changes
+     * @param pipChange TransitionInfo.Change indicating the task that is being pipped
+     * @param splitMainStageRootId MainStage's rootTaskInfo's id
+     * @param splitSideStageRootId SideStage's rootTaskInfo's id
+     * @param lastPipSplitStage The last stage that {@param pipChange} was in
+     * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null}
+     *         otherwise
+     */
+    @Nullable
+    public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info,
+            TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId,
+            @SplitScreen.StageType int lastPipSplitStage) {
+        int lastPipParentTask = -1;
+        if (lastPipSplitStage == STAGE_TYPE_MAIN) {
+            lastPipParentTask = splitMainStageRootId;
+        } else if (lastPipSplitStage == STAGE_TYPE_SIDE) {
+            lastPipParentTask = splitSideStageRootId;
+        }
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (change == pipChange || !isOpeningMode(change.getMode())) {
+                // Ignore the change/task that's going into Pip or not opening
+                continue;
+            }
+
+            if (change.getTaskInfo().parentTaskId == lastPipParentTask) {
+                return change;
+            }
+        }
+        return null;
+    }
+
     private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
         return change.getTaskInfo() != null
                 && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index d6e64cf..9fc6702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -142,7 +142,8 @@
                     && mSplitHandler.getSplitItemPosition(change.getLastParent())
                     != SPLIT_POSITION_UNDEFINED) {
                 return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
-                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+                        /*replacingPip*/ false);
             }
         }
 
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 4d3c763..6224543 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
@@ -31,7 +31,6 @@
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -567,15 +566,15 @@
         final int mode = change.getMode();
         // Put all the OPEN/SHOW on top
         if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
-            if (isOpening
-                    // This is for when an activity launches while a different transition is
-                    // collecting.
-                    || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+            if (isOpening) {
                 // put on top
                 return zSplitLine + numChanges - i;
-            } else {
+            } else if (isClosing) {
                 // put on bottom
                 return zSplitLine - i;
+            } else {
+                // maintain relative ordering (put all changes in the animating layer)
+                return zSplitLine + numChanges - i;
             }
         } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
             if (isOpening) {
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index f674b06a..c3c74a6 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.app.Service;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -404,6 +405,7 @@
      *
      * @param frame A description of the polling frame.
      */
+    @SuppressLint("OnNameExpected")
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void processPollingFrames(@NonNull List<PollingFrame> frame) {
     }
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
index 4fa8822..ad3b44f 100644
--- a/packages/PrintSpooler/TEST_MAPPING
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -8,5 +8,10 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "PrintSpoolerOutOfProcessTests"
+    }
   ]
 }
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
index 30cb993..a762ad3 100644
--- a/packages/SettingsLib/DataStore/README.md
+++ b/packages/SettingsLib/DataStore/README.md
@@ -1,55 +1,93 @@
 # Datastore library
 
-This library aims to manage datastore in a consistent way.
+This library provides consistent API for data management (including backup,
+restore, and metrics logging) on Android platform.
+
+Notably, it is designed to be flexible and could be utilized for a wide range of
+data store besides the settings preferences.
 
 ## Overview
 
-A datastore is required to extend the `BackupRestoreStorage` class and implement
-either `Observable` or `KeyedObservable` interface, which enforces:
+In the high-level design, a persistent datastore aims to support two key
+characteristics:
 
--   Backup and restore: Datastore should support
-    [data backup](https://developer.android.com/guide/topics/data/backup) to
-    preserve user experiences on a new device.
--   Observer pattern: The
-    [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
-    monitor data change in the datastore and
-    -   trigger
-        [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
-        automatically.
-    -   track data change event to log metrics.
-    -   update internal state and take action.
+-   **observable**: triggers backup and metrics logging whenever data is
+    changed.
+-   **transferable**: offers users with a seamless experience by backing up and
+    restoring data on to new devices.
+
+More specifically, Android framework supports
+[data backup](https://developer.android.com/guide/topics/data/backup) to
+preserve user experiences on a new device. And the
+[observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+monitor data change.
 
 ### Backup and restore
 
-The Android backup framework provides
+Currently, the Android backup framework provides
 [BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
 and
 [BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
-to back up a datastore. However, there are several caveats when implement
-`BackupHelper`:
+to facilitate data backup. However, there are several caveats to consider when
+implementing `BackupHelper`:
 
--   performBackup: The data is updated incrementally but it is not well
+-   *performBackup*: The data is updated incrementally but it is not well
     documented. The `ParcelFileDescriptor` state parameters are normally ignored
     and data is updated even there is no change.
--   restoreEntity: The implementation must take care not to seek or close the
-    underlying data source, nor read more than size() bytes from the stream when
-    restore (see
+-   *restoreEntity*: The implementation must take care not to seek or close the
+    underlying data source, nor read more than `size()` bytes from the stream
+    when restore (see
     [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
-    It is possible a `BackupHelper` prevents other `BackupHelper`s from
-    restoring data.
--   writeNewStateDescription: Existing implementations rarely notice that this
-    callback is invoked after all entities are restored, and check if necessary
-    data are all restored in `restoreEntity` (e.g.
+    It is possible that a `BackupHelper` interferes with the restore process of
+    other `BackupHelper`s.
+-   *writeNewStateDescription*: Existing implementations rarely notice that this
+    callback is invoked after *all* entities are restored. Instead, they check
+    if necessary data are all restored in the `restoreEntity` (e.g.
     [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
     which is not robust sometimes.
 
-This library provides more clear API and offers some improvements:
+The datastore library will mitigate these problems by providing alternative
+APIs. For instance, library users make use of `InputStream` / `OutputStream` to
+back up and restore data directly.
 
--   The implementation only needs to focus on the `BackupRestoreEntity`
-    interface. The `InputStream` of restore will ensure bounded data are read,
-    and close the stream will be no-op.
--   The library computes checksum of the backup data automatically, so that
-    unchanged data will not be sent to Android backup system.
+### Observer pattern
+
+In the current implementation, the Android backup framework requires a manual
+call to
+[BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)).
+However, it's often observed that this API call is forgotten when using
+`SharedPreferences`. Additionally, there's a common need to log metrics when
+data changed. To address these limitations, datastore API employed the observer
+pattern.
+
+### API design and advantages
+
+Datastore must extend the `BackupRestoreStorage` class (subclass of
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)).
+The data in a datastore is group by entity, which is represented by
+`BackupRestoreEntity`. Basically, a datastore implementation only needs to focus
+on the `BackupRestoreEntity`.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. There are builtin thread-safe implementations of the
+two interfaces (`KeyedDataObservable` / `DataObservable`). If it is Kotlin, use
+delegation to simplify the code.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`
+directly. To back up other file based storage, extend the
+`BackupRestoreFileStorage` class.
+
+Here are some highlights of the library:
+
+-   The restore `InputStream` will ensure bounded data are read, and close the
+    stream is no-op. That being said, all entities are isolated.
+-   Data checksum is computed automatically, unchanged data will not be sent to
+    Android backup system.
 -   Data compression is supported:
     -   ZIP best compression is enabled by default, no extra effort needs to be
         taken.
@@ -67,98 +105,159 @@
     successfully restored in those older versions. This is achieved by extending
     the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
     treat each file as an entity and do the backup / restore.
--   Manual `BackupManager.dataChanged` call is unnecessary now, the library will
-    do the invocation (see next section).
+-   Manual `BackupManager.dataChanged` call is unnecessary now, the framework
+    will invoke the API automatically.
 
-### Observer pattern
+## Usages
 
-Manual `BackupManager.dataChanged` call is required by current backup framework.
-In practice, it is found that `SharedPreferences` usages foget to invoke the
-API. Besides, there are common use cases to log metrics when data is changed.
-Consequently, observer pattern is employed to resolve the issues.
+This section provides [examples](example/ExampleStorage.kt) of datastore.
 
-If the datastore is key-value based (e.g. `SharedPreferences`), implements the
-`KeyedObservable` interface to offer fine-grained observer. Otherwise,
-implements `Observable`. The library provides thread-safe implementations
-(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
-helpful.
-
-Keep in mind that the implementation should call `KeyedObservable.notifyChange`
-/ `Observable.notifyChange` whenever internal data is changed, so that the
-registered observer will be notified properly.
-
-## Usage and example
-
-For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
-back up other file based storage, extend the `BackupRestoreFileStorage` class.
-
-Here is an example of customized datastore, which has a string to back up:
+Here is a datastore with a string data:
 
 ```kotlin
-class MyDataStore : ObservableBackupRestoreStorage() {
-    // Another option is make it a StringEntity type and maintain a String field inside StringEntity
-    @Volatile // backup/restore happens on Binder thread
-    var data: String? = null
-        private set
+class ExampleStorage : ObservableBackupRestoreStorage() {
+  @Volatile // field is manipulated by multiple threads, synchronization might be needed
+  var data: String? = null
+    private set
 
-    fun setData(data: String?) {
-        this.data = data
-        notifyChange(ChangeReason.UPDATE)
+  @AnyThread
+  fun setData(data: String?) {
+    this.data = data
+    // call notifyChange to trigger backup and metrics logging whenever data is changed
+    if (data != null) {
+      notifyChange(ChangeReason.UPDATE)
+    } else {
+      notifyChange(ChangeReason.DELETE)
     }
-
-    override val name: String
-        get() = "MyData"
-
-    override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
-        listOf(StringEntity("data"))
-
-    private inner class StringEntity(override val key: String) : BackupRestoreEntity {
-        override fun backup(
-            backupContext: BackupContext,
-            outputStream: OutputStream,
-        ) =
-            if (data != null) {
-                outputStream.write(data!!.toByteArray(UTF_8))
-                EntityBackupResult.UPDATE
-            } else {
-                EntityBackupResult.DELETE
-            }
-
-        override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
-            data = String(inputStream.readAllBytes(), UTF_8)
-            // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
-        }
-    }
-
-    override fun onRestoreFinished() {
-        // TODO: Update state with the restored data. Use this callback instead "restore()" in case
-        //       the restore action involves several entities.
-        // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
-    }
-}
-```
-
-In the application class:
-
-```kotlin
-class MyApplication : Application() {
-  override fun onCreate() {
-    super.onCreate();
-    BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
   }
-}
-```
 
-In the custom `BackupAgentHelper` class:
+  override val name: String
+    get() = "ExampleStorage"
 
-```kotlin
-class MyBackupAgentHelper : BackupAgentHelper() {
-  override fun onCreate() {
-    BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+  override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+    listOf(StringEntity("data"))
+
+  override fun enableRestore(): Boolean {
+    return true // check condition like flag, environment, etc.
+  }
+
+  override fun enableBackup(backupContext: BackupContext): Boolean {
+    return true // check condition like flag, environment, etc.
+  }
+
+  @BinderThread
+  private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+    override fun backup(backupContext: BackupContext, outputStream: OutputStream) =
+      if (data != null) {
+        outputStream.write(data!!.toByteArray(UTF_8))
+        EntityBackupResult.UPDATE
+      } else {
+        EntityBackupResult.DELETE // delete existing backup data
+      }
+
+    override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+      // DO NOT call setData API here, which will trigger notifyChange unexpectedly.
+      // Under the hood, the datastore library will call notifyChange(ChangeReason.RESTORE)
+      // later to notify observers.
+      data = String(inputStream.readBytes(), UTF_8)
+      // Handle restored data in onRestoreFinished() callback
+    }
   }
 
   override fun onRestoreFinished() {
-    BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+    // TODO: Update state with the restored data. Use this callback instead of "restore()" in
+    //       case the restore action involves several entities.
+    // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
   }
 }
 ```
+
+And this is a datastore with key value data:
+
+```kotlin
+class ExampleKeyValueStorage :
+  BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() {
+  // thread safe data structure
+  private val map = ConcurrentHashMap<String, String>()
+
+  override val name: String
+    get() = "ExampleKeyValueStorage"
+
+  fun updateData(key: String, value: String?) {
+    if (value != null) {
+      map[key] = value
+      notifyChange(ChangeReason.UPDATE)
+    } else {
+      map.remove(key)
+      notifyChange(ChangeReason.DELETE)
+    }
+  }
+
+  override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+    listOf(createMapBackupRestoreEntity())
+
+  private fun createMapBackupRestoreEntity() =
+    object : BackupRestoreEntity {
+      override val key: String
+        get() = "map"
+
+      override fun backup(
+        backupContext: BackupContext,
+        outputStream: OutputStream,
+      ): EntityBackupResult {
+        // Use TreeMap to achieve predictable and stable order, so that data will not be
+        // updated to Android backup backend if there is only order change.
+        val copy = TreeMap(map)
+        if (copy.isEmpty()) return EntityBackupResult.DELETE
+        val dataOutputStream = DataOutputStream(outputStream)
+        dataOutputStream.writeInt(copy.size)
+        for ((key, value) in copy) {
+          dataOutputStream.writeUTF(key)
+          dataOutputStream.writeUTF(value)
+        }
+        return EntityBackupResult.UPDATE
+      }
+
+      override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+        val dataInputString = DataInputStream(inputStream)
+        repeat(dataInputString.readInt()) {
+          val key = dataInputString.readUTF()
+          val value = dataInputString.readUTF()
+          map[key] = value
+        }
+      }
+    }
+}
+```
+
+All the datastore should be added in the application class:
+
+```kotlin
+class ExampleApplication : Application() {
+  override fun onCreate() {
+    super.onCreate()
+    BackupRestoreStorageManager.getInstance(this)
+      .add(ExampleStorage(), ExampleKeyValueStorage())
+  }
+}
+```
+
+Additionally, inject datastore to the custom `BackupAgentHelper` class:
+
+```kotlin
+class ExampleBackupAgent : BackupAgentHelper() {
+  override fun onCreate() {
+    super.onCreate()
+    BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this)
+  }
+
+  override fun onRestoreFinished() {
+    BackupRestoreStorageManager.getInstance(this).onRestoreFinished()
+  }
+}
+```
+
+## Development
+
+Please preserve the code coverage ratio during development. The current line
+coverage is **100% (444/444)** and branch coverage is **93.6% (176/188)**.
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
index 817ee4c..6720e5c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
@@ -23,7 +23,11 @@
 import java.io.InputStream
 import java.io.OutputStream
 
-/** Entity for back up and restore. */
+/**
+ * Entity for back up and restore.
+ *
+ * Note that backup/restore callback is invoked on the binder thread.
+ */
 interface BackupRestoreEntity {
     /**
      * Key of the entity.
@@ -45,9 +49,12 @@
     /**
      * Backs up the entity.
      *
+     * Back up data in predictable order (e.g. use `TreeMap` instead of `HashMap`), otherwise data
+     * will be backed up needlessly.
+     *
      * @param backupContext context for backup
      * @param outputStream output stream to back up data
-     * @return false if backup file is deleted, otherwise true
+     * @return backup result
      */
     @BinderThread
     @Throws(IOException::class)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index 935f9cc..284c97b 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -22,6 +22,7 @@
 import android.app.backup.BackupHelper
 import android.os.ParcelFileDescriptor
 import android.util.Log
+import androidx.annotation.BinderThread
 import androidx.annotation.VisibleForTesting
 import androidx.collection.MutableScatterMap
 import com.google.common.io.ByteStreams
@@ -38,16 +39,22 @@
 import java.util.zip.CheckedInputStream
 import java.util.zip.CheckedOutputStream
 import java.util.zip.Checksum
+import javax.annotation.concurrent.ThreadSafe
 
 internal const val LOG_TAG = "BackupRestoreStorage"
 
 /**
- * Storage with backup and restore support. Subclass must implement either [Observable] or
- * [KeyedObservable] interface.
+ * Storage with backup and restore support.
+ *
+ * Subclass MUST
+ * - implement either [Observable] or [KeyedObservable] interface.
+ * - be thread safe, backup/restore happens on Binder thread, while general data read/write
+ *   operations occur on other threads.
  *
  * The storage is identified by a unique string [name] and data set is split into entities
  * ([BackupRestoreEntity]).
  */
+@ThreadSafe
 abstract class BackupRestoreStorage : BackupHelper {
     /**
      * A unique string used to disambiguate the various storages within backup agent.
@@ -68,7 +75,7 @@
     @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null
 
     /** Entities to back up and restore. */
-    abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
+    @BinderThread abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
 
     /** Default codec used to encode/decode the entity data. */
     open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
@@ -134,7 +141,11 @@
         Log.i(LOG_TAG, "[$name] Backup end")
     }
 
-    /** Returns if backup is enabled. */
+    /**
+     * Returns if backup is enabled.
+     *
+     * If disabled, [performBackup] will be no-op, all entities backup are skipped.
+     */
     open fun enableBackup(backupContext: BackupContext): Boolean = true
 
     open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream {
@@ -172,7 +183,11 @@
     private fun ensureEntities(): List<BackupRestoreEntity> =
         entities ?: createBackupRestoreEntities().also { entities = it }
 
-    /** Returns if restore is enabled. */
+    /**
+     * Returns if restore is enabled.
+     *
+     * If disabled, [restoreEntity] will be no-op, all entities restore are skipped.
+     */
     open fun enableRestore(): Boolean = true
 
     open fun wrapRestoreInputStream(
@@ -188,12 +203,13 @@
     }
 
     final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
+        if (!enableRestore()) return
         entities = null // clear to reduce memory footprint
         newState.writeAndClearEntityStates()
         onRestoreFinished()
     }
 
-    /** Callbacks when restore finished. */
+    /** Callbacks when entity data are all restored. */
     open fun onRestoreFinished() {}
 
     @VisibleForTesting
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
index 99998ff..26534ba 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
@@ -248,6 +248,15 @@
     }
 
     @Test
+    fun writeNewStateDescription_restoreDisabled() {
+        val storage = spy(TestStorage().apply { enabled = false })
+        temporaryFolder.newFile().toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+            storage.writeNewStateDescription(it)
+        }
+        verify(storage, never()).onRestoreFinished()
+    }
+
+    @Test
     fun backupAndRestore() {
         val storage = spy(TestStorage(entity1, entity2))
         val backupAgentHelper = BackupAgentHelper()
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
index 4ced9f2..cece966 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -16,8 +16,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
-    <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
-    <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
-    <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+    <item android:state_pressed="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+    <item android:state_selected="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+    <item android:state_activated="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+    <item android:color="@color/settingslib_materialColorSurfaceBright"/> <!-- not selected -->
 </selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
index 285ab73..eba9c2c 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -19,12 +19,12 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:radius="?android:attr/dialogCornerRadius" />
+                android:radius="@dimen/settingslib_preference_corner_radius" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
index e417307..5c60f37 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -19,15 +19,15 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:topLeftRadius="0dp"
-                android:bottomLeftRadius="?android:attr/dialogCornerRadius"
-                android:topRightRadius="0dp"
-                android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+                android:topLeftRadius="4dp"
+                android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:topRightRadius="4dp"
+                android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
new file mode 100644
index 0000000..de64efd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:topLeftRadius="4dp"
+                android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:topRightRadius="4dp"
+                android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
index e964657..dd70f4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -19,12 +19,12 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:radius="1dp" />
+                android:radius="4dp" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
new file mode 100644
index 0000000..fffc6c8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:radius="4dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
new file mode 100644
index 0000000..f83e3b1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:radius="@dimen/settingslib_preference_corner_radius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
index a9d69c2..ab79d18 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -19,15 +19,15 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:topLeftRadius="?android:attr/dialogCornerRadius"
-                android:bottomLeftRadius="0dp"
-                android:topRightRadius="?android:attr/dialogCornerRadius"
-                android:bottomRightRadius="0dp" />
+                android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomLeftRadius="4dp"
+                android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomRightRadius="4dp" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
new file mode 100644
index 0000000..112ec73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomLeftRadius="4dp"
+                android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomRightRadius="4dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
new file mode 100644
index 0000000..d783956
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <dimen name="settingslib_preference_corner_radius">20dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
new file mode 100644
index 0000000..d69c87b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.settingslib.satellite
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.telephony.satellite.SatelliteManager
+import android.util.Log
+import android.view.WindowManager
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settingslib.wifi.WifiUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.Default
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+import kotlin.coroutines.resume
+
+/** A util for Satellite dialog */
+object SatelliteDialogUtils {
+
+    /**
+     * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+     * Wifi during the satellite mode is on.
+     */
+    @JvmStatic
+    fun mayStartSatelliteWarningDialog(
+            context: Context,
+            lifecycleOwner: LifecycleOwner,
+            type: Int,
+            allowClick: (isAllowed: Boolean) -> Unit
+    ): Job {
+        return mayStartSatelliteWarningDialog(
+                context, lifecycleOwner.lifecycleScope, type, allowClick)
+    }
+
+    /**
+     * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+     * Wifi during the satellite mode is on.
+     */
+    @JvmStatic
+    fun mayStartSatelliteWarningDialog(
+            context: Context,
+            coroutineScope: CoroutineScope,
+            type: Int,
+            allowClick: (isAllowed: Boolean) -> Unit
+    ): Job =
+            coroutineScope.launch {
+                var isSatelliteModeOn = false
+                try {
+                    isSatelliteModeOn = requestIsEnabled(context)
+                } catch (e: InterruptedException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                } catch (e: ExecutionException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                } catch (e: TimeoutException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                }
+
+                if (isSatelliteModeOn) {
+                    startSatelliteWarningDialog(context, type)
+                }
+                withContext(Dispatchers.Main) {
+                    allowClick(!isSatelliteModeOn)
+                }
+            }
+
+    private fun startSatelliteWarningDialog(context: Context, type: Int) {
+        context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+            component = ComponentName(
+                    "com.android.settings",
+                    "com.android.settings.network.SatelliteWarningDialogActivity"
+            )
+            putExtra(WifiUtils.DIALOG_WINDOW_TYPE,
+                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+            putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type)
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+        })
+    }
+
+    /**
+     * Checks if the satellite modem is enabled.
+     *
+     * @param executor The executor to run the asynchronous operation on
+     * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled,
+     *         `false` otherwise.
+     */
+    private suspend fun requestIsEnabled(
+            context: Context,
+    ): Boolean = withContext(Default) {
+        val satelliteManager: SatelliteManager? =
+                context.getSystemService(SatelliteManager::class.java)
+        if (satelliteManager == null) {
+            Log.w(TAG, "SatelliteManager is null")
+            return@withContext false
+        }
+
+        suspendCancellableCoroutine {continuation ->
+            satelliteManager?.requestIsEnabled(Default.asExecutor(),
+                    object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+                        override fun onResult(result: Boolean) {
+                            Log.i(TAG, "Satellite modem enabled status: $result")
+                            continuation.resume(result)
+                        }
+
+                        override fun onError(error: SatelliteManager.SatelliteException) {
+                            super.onError(error)
+                            Log.w(TAG, "Can't get satellite modem enabled status", error)
+                            continuation.resume(false)
+                        }
+                    })
+        }
+    }
+
+    const val TAG = "SatelliteDialogUtils"
+
+    const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
+            "extra_type_of_satellite_warning_dialog"
+    const val TYPE_IS_UNKNOWN = -1
+    const val TYPE_IS_WIFI = 0
+    const val TYPE_IS_BLUETOOTH = 1
+    const val TYPE_IS_AIRPLANE_MODE = 2
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
index 2a44511..a939ed1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.statusbar.notification.data.repository
 
 import android.app.NotificationManager
+import android.provider.Settings
 import com.android.settingslib.statusbar.notification.data.model.ZenMode
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -28,10 +29,14 @@
     override val notificationPolicy: StateFlow<NotificationManager.Policy?>
         get() = mutableNotificationPolicy.asStateFlow()
 
-    private val mutableZenMode = MutableStateFlow<ZenMode?>(null)
+    private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF))
     override val zenMode: StateFlow<ZenMode?>
         get() = mutableZenMode.asStateFlow()
 
+    init {
+        updateNotificationPolicy()
+    }
+
     fun updateNotificationPolicy(policy: NotificationManager.Policy?) {
         mutableNotificationPolicy.value = policy
     }
@@ -48,13 +53,14 @@
     suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
     state: Int = NotificationManager.Policy.STATE_UNSET,
     priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
-) = updateNotificationPolicy(
-    NotificationManager.Policy(
-        priorityCategories,
-        priorityCallSenders,
-        priorityMessageSenders,
-        suppressedVisualEffects,
-        state,
-        priorityConversationSenders,
+) =
+    updateNotificationPolicy(
+        NotificationManager.Policy(
+            priorityCategories,
+            priorityCallSenders,
+            priorityMessageSenders,
+            suppressedVisualEffects,
+            state,
+            priorityConversationSenders,
+        )
     )
-)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 65a5317..36e396fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -72,7 +72,11 @@
 
     suspend fun setVolume(audioStream: AudioStream, volume: Int)
 
-    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
+    /**
+     * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the
+     * otherwise.
+     */
+    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
 
     suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
 
@@ -164,14 +168,20 @@
             audioManager.setStreamVolume(audioStream.value, volume, 0)
         }
 
-    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
-        withContext(backgroundCoroutineContext) {
-            audioManager.adjustStreamVolume(
-                audioStream.value,
-                if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
-                0,
-            )
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+        return withContext(backgroundCoroutineContext) {
+            if (isMuted == audioManager.isStreamMute(audioStream.value)) {
+                false
+            } else {
+                audioManager.adjustStreamVolume(
+                    audioStream.value,
+                    if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+                    0,
+                )
+                true
+            }
         }
+    }
 
     override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
         withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 33f917e..0e5ebda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -25,6 +25,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 
 /** Provides audio stream state and an ability to change it */
@@ -46,8 +47,16 @@
     val ringerMode: StateFlow<RingerMode>
         get() = audioRepository.ringerMode
 
-    suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+    suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+        val streamModel = getAudioStream(audioStream).first()
+        val oldVolume = streamModel.volume
         audioRepository.setVolume(audioStream, volume)
+        when {
+            volume == streamModel.minVolume -> setMuted(audioStream, true)
+            oldVolume == streamModel.minVolume && volume > streamModel.minVolume ->
+                setMuted(audioStream, false)
+        }
+    }
 
     suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
         if (audioStream.value == AudioManager.STREAM_RING) {
@@ -55,7 +64,16 @@
                 if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
             audioRepository.setRingerMode(audioStream, RingerMode(mode))
         }
-        audioRepository.setMuted(audioStream, isMuted)
+        val mutedChanged = audioRepository.setMuted(audioStream, isMuted)
+        if (mutedChanged && !isMuted) {
+            with(getAudioStream(audioStream).first()) {
+                if (volume == minVolume) {
+                    // Slightly increase volume when user un-mutes the stream that is lowered
+                    // down to its minimum
+                    setVolume(audioStream, volume + 1)
+                }
+            }
+        }
     }
 
     /** Checks if the volume can be changed via the UI. */
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index f87b519..e125083 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -41,7 +41,10 @@
 //###########################################################
 android_robolectric_test {
     name: "SettingsLibRoboTests",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     static_libs: [
         "Settings_robolectric_meta_service_file",
         "Robolectric_shadows_androidx_fragment_upstream",
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
new file mode 100644
index 0000000..aeda1ed6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.settingslib.satellite
+
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.util.AndroidRuntimeException
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.verify
+import org.mockito.internal.verification.Times
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SatelliteDialogUtilsTest {
+    @JvmField
+    @Rule
+    val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    var context: Context = ApplicationProvider.getApplicationContext()
+    @Mock
+    private lateinit var satelliteManager: SatelliteManager
+
+    private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+    @Before
+    fun setUp() {
+        `when`(context.getSystemService(SatelliteManager::class.java))
+                .thenReturn(satelliteManager)
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onResult(true)
+                    null
+                }
+
+        try {
+            SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                        assertTrue(it)
+                })
+        } catch (e: AndroidRuntimeException) {
+            // Catch exception of starting activity .
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onResult(false)
+                    null
+                }
+
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
+        `when`(context.getSystemService(SatelliteManager::class.java))
+                .thenReturn(null)
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
+                    null
+                }
+
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46bf494..374240b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -347,6 +347,7 @@
     <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
     <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
     <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+    <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b9e70ef..9c58371 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -826,20 +826,6 @@
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".contrast.ContrastDialogActivity"
-            android:label="@string/quick_settings_contrast_label"
-            android:theme="@style/Theme.SystemUI.ContrastDialog"
-            android:finishOnCloseSystemDialogs="true"
-            android:launchMode="singleInstance"
-            android:excludeFromRecents="true"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".ForegroundServicesDialog"
             android:process=":fgservices"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index aff86e8a1..10ba7bd 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -204,7 +204,16 @@
     description: "Re-enable the codepath that removed tinting of notifications when the"
         " standard background color is desired.  This was the behavior before we discovered"
         " a resources threading issue, which we worked around by tinting the notification"
-        " backgrounds and footer buttons."
+        " backgrounds."
+    bug: "294830092"
+}
+
+flag {
+    name: "notification_footer_background_tint_optimization"
+    namespace: "systemui"
+    description: "Remove duplicative tinting of notification footer buttons. This was the behavior"
+        " before we discovered a resources threading issue, which we worked around by applying the"
+        " same color as a tint to the background drawable of footer buttons."
     bug: "294830092"
 }
 
@@ -355,6 +364,14 @@
 }
 
 flag {
+    name: "status_bar_screen_sharing_chips"
+    namespace: "systemui"
+    description: "Show chips on the left side of the status bar when a user is screen sharing, "
+        "recording, or casting"
+    bug: "332662551"
+}
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -496,6 +513,15 @@
     }
 }
 
+flag {
+    name: "screenshot_scroll_crop_view_crash_fix"
+    namespace: "systemui"
+    description: "Mitigate crash on invalid computed range in CropView"
+    bug: "232633995"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
 
 flag {
     name: "screenshot_private_profile_behavior_fix"
@@ -943,3 +969,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "glanceable_hub_gesture_handle"
+  namespace: "systemui"
+  description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub"
+  bug: "339667383"
+}
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 8ee8ea4..feb1f5b 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
@@ -2,16 +2,24 @@
 
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
@@ -26,6 +34,7 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
+import com.android.systemui.Flags
 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
@@ -88,6 +97,8 @@
     val currentSceneKey: SceneKey by
         viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
     val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
+    val showGestureIndicator by
+        viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
             initialScene = currentSceneKey,
@@ -126,7 +137,19 @@
                 )
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
-            Box(modifier = Modifier.fillMaxSize())
+            // TODO(b/339667383): remove this temporary swipe gesture handle
+            Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) {
+                if (showGestureIndicator && Flags.glanceableHubGestureHandle()) {
+                    Box(
+                        modifier =
+                            Modifier.height(220.dp)
+                                .width(4.dp)
+                                .align(Alignment.CenterVertically)
+                                .background(color = Color.White, RoundedCornerShape(4.dp))
+                    )
+                    Spacer(modifier = Modifier.width(12.dp))
+                }
+            }
         }
 
         scene(
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 cb3867f..271eb96 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
@@ -78,12 +78,7 @@
                     }
 
                     state.a11yStateDescription?.let { stateDescription = it }
-                        ?: run {
-                            // provide a not animated value to the a11y because it fails to announce
-                            // the settled value when it changes rapidly.
-                            progressBarRangeInfo =
-                                ProgressBarRangeInfo(state.value, state.valueRange)
-                        }
+                    progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
                 } else {
                     disabled()
                     contentDescription =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index ecfcc90..a5acf72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -66,15 +66,12 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val sceneContainerStartable = kosmos.sceneContainerStartable
 
     private lateinit var underTest: BouncerViewModel
 
     @Before
     fun setUp() {
-        sceneContainerStartable.start()
+        kosmos.sceneContainerStartable.start()
         underTest = kosmos.bouncerViewModel
     }
 
@@ -164,11 +161,11 @@
             assertThat(isInputEnabled).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
-                bouncerInteractor.authenticate(WRONG_PIN)
+                kosmos.bouncerInteractor.authenticate(WRONG_PIN)
             }
             assertThat(isInputEnabled).isFalse()
 
-            val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+            val lockoutEndMs = kosmos.authenticationInteractor.lockoutEndTimestamp ?: 0
             advanceTimeBy(lockoutEndMs - testScope.currentTime)
             assertThat(isInputEnabled).isTrue()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
index 312c14d..fec56ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -18,9 +18,13 @@
 
 import android.content.applicationContext
 import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
 import androidx.test.filters.SmallTest
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testDispatcher
@@ -40,15 +44,19 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     private val kosmos = testKosmos()
 
-    private val fakeUserRepository = kosmos.fakeUserRepository
-
     private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
         whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
         whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
@@ -130,7 +138,83 @@
             }
         }
 
-    private companion object {
-        val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+    @Test
+    @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+    fun brightnessBaseUserRestriction_flagOff_noRestriction() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+            }
+        }
+
+    @Test
+    fun bothRestrictions_returnsSetEnforcedAdminFromCheck() =
+        with(kosmos) {
+            testScope.runTest {
+                val enforcedAdmin: EnforcedAdmin =
+                    EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+                whenever(
+                        mockUserRestrictionChecker.checkIfRestrictionEnforced(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(enforcedAdmin)
+
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+            }
+        }
+
+    @Test
+    @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+    fun brightnessBaseUserRestriction_flagOn_emptyRestriction() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
+            }
+        }
+
+    companion object {
+        private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
index 85a4bcf..11f5238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
@@ -48,7 +48,6 @@
     private val kosmos = testKosmos()
 
     private val mockActivityStarter = kosmos.activityStarter
-    private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
 
     private val underTest =
         with(kosmos) {
@@ -70,7 +69,18 @@
 
                 fakeBrightnessPolicyRepository.setCurrentUserRestricted()
 
-                assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+                assertThat(restriction)
+                    .isEqualTo(
+                        PolicyRestriction.Restricted(
+                            EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                                BrightnessPolicyRepository.RESTRICTION
+                            )
+                        )
+                    )
+
+                fakeBrightnessPolicyRepository.setBaseUserRestriction()
+
+                assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 2d079d7..be44339 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 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.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -141,6 +142,7 @@
                 testScope,
                 context.resources,
                 kosmos.keyguardTransitionInteractor,
+                kosmos.keyguardInteractor,
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 8bfa5cf..f5c86e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -31,8 +31,11 @@
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.AttachedSurfaceControl;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
@@ -42,6 +45,7 @@
 
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -94,6 +98,9 @@
     ViewGroup mDreamOverlayContentView;
 
     @Mock
+    View mHubGestureIndicatorView;
+
+    @Mock
     Handler mHandler;
 
     @Mock
@@ -142,6 +149,7 @@
                 mDreamOverlayContainerView,
                 mComplicationHostViewController,
                 mDreamOverlayContentView,
+                mHubGestureIndicatorView,
                 mDreamOverlayStatusBarViewController,
                 mLowLightTransitionCoordinator,
                 mBlurUtils,
@@ -161,6 +169,18 @@
                 mDreamManager);
     }
 
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    @Test
+    public void testHubGestureIndicatorGoneWhenFlagOff() {
+        verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE);
+    }
+
+    @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE})
+    @Test
+    public void testHubGestureIndicatorVisibleWhenFlagOn() {
+        verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE);
+    }
+
     @Test
     public void testRootSurfaceControlInsetSetOnAttach() {
         mController.onViewAttached();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
new file mode 100644
index 0000000..1e7ed63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FingerprintPropertyRepositoryTest : SysuiTestCase() {
+    @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+    private val testScope = TestScope()
+    private lateinit var underTest: FingerprintPropertyRepositoryImpl
+    @Mock private lateinit var fingerprintManager: FingerprintManager
+    @Captor
+    private lateinit var fingerprintCallbackCaptor:
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
+
+    @Before
+    fun setup() {
+        underTest =
+            FingerprintPropertyRepositoryImpl(
+                testScope.backgroundScope,
+                Dispatchers.Main.immediate,
+                fingerprintManager,
+            )
+    }
+
+    @Test
+    fun propertiesInitialized_onStartFalse() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+        }
+
+    @Test
+    fun propertiesInitialized_onStartTrue() =
+        testScope.runTest {
+            //            // collect sensorType to update fingerprintCallback before
+            // propertiesInitialized
+            //            // is listened for
+            val sensorType by collectLastValue(underTest.sensorType)
+            runCurrent()
+            captureFingerprintCallback()
+
+            fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    @Test
+    fun propertiesInitialized_updatedToTrue() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+
+            captureFingerprintCallback()
+            fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    private fun captureFingerprintCallback() {
+        verify(fingerprintManager)
+            .addAuthenticatorsRegisteredCallback(fingerprintCallbackCaptor.capture())
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index cb2d4e0..addbdb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -60,17 +60,19 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeKeyguardRepository
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
-    private val commandQueue = kosmos.fakeCommandQueue
-    private val configRepository = kosmos.fakeConfigurationRepository
-    private val bouncerRepository = kosmos.keyguardBouncerRepository
-    private val shadeRepository = kosmos.shadeRepository
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val repository by lazy { kosmos.fakeKeyguardRepository }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+    private val commandQueue by lazy { kosmos.fakeCommandQueue }
+    private val configRepository by lazy { kosmos.fakeConfigurationRepository }
+    private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
+    private val shadeRepository by lazy { kosmos.shadeRepository }
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
         MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
-    private val underTest = kosmos.keyguardInteractor
+
+    private val underTest by lazy { kosmos.keyguardInteractor }
 
     @Before
     fun setUp() {
@@ -275,6 +277,28 @@
         }
 
     @Test
+    fun keyguardTranslationY_whenNotGoneAndShadeIsReesetEmitsZero() =
+        testScope.runTest {
+            val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+            configRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                100
+            )
+            configRepository.onAnyConfigurationChange()
+
+            shadeRepository.setLegacyShadeExpansion(1f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            assertThat(keyguardTranslationY).isEqualTo(0f)
+        }
+
+    @Test
     fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() =
         testScope.runTest {
             val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index 4226a9d..0551bfb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -109,7 +109,7 @@
             assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
             assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
 
-            underTest.onAttached()
+            underTest.onReorderingAllowed()
 
             mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
             mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index 5661bd3..9d8ec95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -51,8 +51,8 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
 
     private val underTest = kosmos.notificationsShadeSceneViewModel
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bf48784..02a8141 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -69,6 +69,15 @@
     }
 
     @Test
+    fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() {
+        val intent = Intent("test.ACTION")
+
+        underTest.handle(null, intent, true)
+
+        verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true))
+    }
+
+    @Test
     fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
         val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
new file mode 100644
index 0000000..c41ce2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileDataInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.of(1)!!
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val testIntent = mock<Intent>()
+    private val qrCodeScannerController =
+        mock<QRCodeScannerController> {
+            whenever(intent).thenReturn(testIntent)
+            whenever(isAbleToLaunchScannerActivity).thenReturn(false)
+        }
+    private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent)
+    private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+    private val underTest: QRCodeScannerTileDataInteractor =
+        QRCodeScannerTileDataInteractor(
+            testDispatcher,
+            scope.backgroundScope,
+            qrCodeScannerController,
+        )
+
+    @Test
+    fun availability_matchesController_cameraNotAvailable() =
+        scope.runTest {
+            val expectedAvailability = false
+            whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false)
+
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isEqualTo(expectedAvailability)
+        }
+
+    @Test
+    fun availability_matchesController_cameraIsAvailable() =
+        scope.runTest {
+            val expectedAvailability = true
+            whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true)
+
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isEqualTo(expectedAvailability)
+        }
+
+    @Test
+    fun data_matchesController() =
+        scope.runTest {
+            val captor = argumentCaptor<Callback>()
+            val lastData by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+
+            verify(qrCodeScannerController).addCallback(captor.capture())
+            val callback = captor.value
+
+            assertThat(lastData!!).isEqualTo(testUnavailableModel)
+
+            whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true)
+            callback.onQRCodeScannerActivityChanged()
+            runCurrent()
+            assertThat(lastData!!).isEqualTo(testAvailableModel)
+
+            whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false)
+            callback.onQRCodeScannerActivityChanged()
+            runCurrent()
+            assertThat(lastData!!).isEqualTo(testUnavailableModel)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..312f180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() {
+    val kosmos = Kosmos()
+    private val inputHandler = kosmos.qsTileIntentUserInputHandler
+    private val underTest = kosmos.qrCodeScannerTileUserActionInteractor
+    private val intent = mock<Intent>()
+
+    @Test
+    fun handleClick_available() = runTest {
+        val inputModel = QRCodeScannerTileModel.Available(intent)
+
+        underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            intent
+        }
+    }
+
+    @Test
+    fun handleClick_temporarilyUnavailable() = runTest {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+
+    @Test
+    fun handleLongClick_available() = runTest {
+        val inputModel = QRCodeScannerTileModel.Available(intent)
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+
+    @Test
+    fun handleLongClick_temporarilyUnavailable() = runTest {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
new file mode 100644
index 0000000..d26a213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.ui
+
+import android.content.Intent
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsQRCodeScannerTileConfig
+
+    private lateinit var mapper: QRCodeScannerTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            QRCodeScannerTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(
+                            com.android.systemui.res.R.drawable.ic_qr_code_scanner,
+                            TestStubDrawable()
+                        )
+                    }
+                    .resources,
+                context.theme
+            )
+    }
+
+    @Test
+    fun availableModel() {
+        val mockIntent = mock<Intent>()
+        val inputModel = QRCodeScannerTileModel.Available(mockIntent)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createQRCodeScannerTileState(
+                QSTileState.ActivationState.INACTIVE,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun temporarilyUnavailableModel() {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createQRCodeScannerTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                context.getString(
+                    com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createQRCodeScannerTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String?,
+    ): QSTileState {
+        val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
+        return QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+                    null
+                )
+            },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
index c75e297..e3108ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
@@ -45,11 +45,11 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val sceneContainerStartable = kosmos.sceneContainerStartable
-    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable }
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
 
-    private val underTest = kosmos.sceneBackInteractor
+    private val underTest by lazy { kosmos.sceneBackInteractor }
 
     @Test
     @EnableSceneContainer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index e11a8f1..851b7b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -52,9 +52,9 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
+    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor }
     private val transitionState =
         MutableStateFlow<ObservableTransitionState>(
             ObservableTransitionState.Idle(Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
index 1cd12f0..7bc6948 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
@@ -35,6 +35,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.verify
@@ -46,30 +47,32 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private val sceneContainerRepository = kosmos.sceneContainerRepository
-    private val keyguardInteractor = kosmos.keyguardInteractor
+    lateinit var underTest: DozeServiceHost
 
-    val underTest =
-        kosmos.dozeServiceHost.apply {
-            initialize(
-                /* centralSurfaces = */ mock(),
-                /* statusBarKeyguardViewManager = */ mock(),
-                /* notificationShadeWindowViewController = */ mock(),
-                /* ambientIndicationContainer = */ mock(),
-            )
-        }
+    @Before
+    fun setup() {
+        underTest =
+            kosmos.dozeServiceHost.apply {
+                initialize(
+                    /* centralSurfaces = */ mock(),
+                    /* statusBarKeyguardViewManager = */ mock(),
+                    /* notificationShadeWindowViewController = */ mock(),
+                    /* ambientIndicationContainer = */ mock(),
+                )
+            }
+    }
 
     @Test
     @EnableSceneContainer
     fun startStopDozing() =
         testScope.runTest {
-            val isDozing by collectLastValue(keyguardInteractor.isDozing)
+            val isDozing by collectLastValue(kosmos.keyguardInteractor.isDozing)
 
             // GIVEN a callback is set
             val callback: DozeHost.Callback = mock()
             underTest.addCallback(callback)
             // AND we are on the lock screen
-            sceneContainerRepository.changeScene(Scenes.Lockscreen)
+            kosmos.sceneContainerRepository.changeScene(Scenes.Lockscreen)
             // AND dozing is not requested yet
             assertThat(underTest.dozingRequested).isFalse()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index 675136c..a163ca0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -36,7 +36,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -47,19 +46,8 @@
 
     private val kosmos = testKosmos()
 
-    private lateinit var underTest: AudioVolumeInteractor
-
-    @Before
-    fun setup() {
-        with(kosmos) {
-            underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
-
-            audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL))
-
-            notificationsSoundPolicyRepository.updateNotificationPolicy()
-            notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF))
-        }
-    }
+    private val underTest: AudioVolumeInteractor =
+        with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) }
 
     @Test
     fun setMuted_mutesStream() {
@@ -236,6 +224,55 @@
         }
     }
 
+    @Test
+    fun testReducingVolumeToMin_mutes() =
+        with(kosmos) {
+            testScope.runTest {
+                val audioStreamModel by
+                    collectLastValue(audioRepository.getAudioStream(audioStream))
+                runCurrent()
+
+                underTest.setVolume(audioStream, audioStreamModel!!.minVolume)
+                runCurrent()
+
+                assertThat(audioStreamModel!!.isMuted).isTrue()
+            }
+        }
+
+    @Test
+    fun testIncreasingVolumeFromMin_unmutes() =
+        with(kosmos) {
+            testScope.runTest {
+                val audioStreamModel by
+                    collectLastValue(audioRepository.getAudioStream(audioStream))
+                audioRepository.setMuted(audioStream, true)
+                audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+                runCurrent()
+
+                underTest.setVolume(audioStream, audioStreamModel!!.maxVolume)
+                runCurrent()
+
+                assertThat(audioStreamModel!!.isMuted).isFalse()
+            }
+        }
+
+    @Test
+    fun testUnmutingMinVolume_increasesVolume() =
+        with(kosmos) {
+            testScope.runTest {
+                val audioStreamModel by
+                    collectLastValue(audioRepository.getAudioStream(audioStream))
+                audioRepository.setMuted(audioStream, true)
+                audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+                runCurrent()
+
+                underTest.setMuted(audioStream, false)
+                runCurrent()
+
+                assertThat(audioStreamModel!!.volume).isGreaterThan(audioStreamModel!!.minVolume)
+            }
+        }
+
     private companion object {
         val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
     }
diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
index a751f58..370677ac 100644
--- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
@@ -16,5 +16,5 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="@color/material_dynamic_neutral20" />
-    <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+    <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
deleted file mode 100644
index 4181220..0000000
--- a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright 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"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
-    <item android:state_selected="true">
-        <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
-            <stroke
-                android:color="?androidprv:attr/colorAccentPrimary"
-                android:width="@dimen/contrast_dialog_button_stroke_width" />
-            <corners android:radius="@dimen/contrast_dialog_button_radius"/>
-        </shape>
-    </item>
-
-    <item>
-        <layer-list>
-            <item android:top="@dimen/contrast_dialog_button_stroke_width"
-                android:bottom="@dimen/contrast_dialog_button_stroke_width"
-                android:left="@dimen/contrast_dialog_button_stroke_width"
-                android:right="@dimen/contrast_dialog_button_stroke_width">
-                <shape android:shape="rectangle">
-                    <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
-                    <corners android:radius="@dimen/contrast_dialog_button_radius"/>
-                </shape>
-            </item>
-        </layer-list>
-    </item>
-</selector>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/hub_handle.xml
similarity index 77%
copy from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
copy to packages/SystemUI/res/drawable/hub_handle.xml
index bdd6270..8bc276f 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/hub_handle.xml
@@ -1,5 +1,5 @@
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,6 +15,6 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorAccent" />
-    <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+    <corners android:radius="4dp" />
+    <solid android:color="#FFFFFF" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml
deleted file mode 100644
index aa5b5ab..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_high.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#F2F1E8"
-        android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z"
-        android:strokeColor="#1B1C17" android:strokeWidth="1"/>
-    <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml
deleted file mode 100644
index 89519b8..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_medium.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
-    <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml
deleted file mode 100644
index f914975..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_standard.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
-    <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
similarity index 90%
rename from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
rename to packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
index bdd6270..b9a4cbf 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
@@ -16,5 +16,5 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="?android:attr/colorAccent" />
-    <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+    <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 5755dcd..01b9f7e 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -26,8 +26,8 @@
         android:paddingVertical="16dp"
         android:visibility="visible"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
-        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+        app:layout_constraintRight_toLeftOf="@+id/rightGuideline"
+        app:layout_constraintLeft_toLeftOf="@+id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topGuideline" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
@@ -35,8 +35,8 @@
         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_constraintRight_toRightOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         tools:srcCompat="@tools:sample/avatars" />
 
@@ -63,8 +63,8 @@
         android:paddingTop="24dp"
         android:fadeScrollbars="false"
         app:layout_constraintBottom_toTopOf="@+id/button_bar"
-        app:layout_constraintEnd_toStartOf="@+id/midGuideline"
-        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintRight_toLeftOf="@+id/midGuideline"
+        app:layout_constraintLeft_toLeftOf="@id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topGuideline">
 
         <androidx.constraintlayout.widget.ConstraintLayout
@@ -89,7 +89,7 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:textAlignment="viewStart"
-                android:paddingLeft="16dp"
+                android:paddingStart="16dp"
                 app:layout_constraintBottom_toBottomOf="@+id/logo"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toEndOf="@+id/logo"
@@ -209,6 +209,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        app:guidelineUseRtl="false"
         app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
 
     <androidx.constraintlayout.widget.Guideline
@@ -216,6 +217,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        app:guidelineUseRtl="false"
         app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
 
     <androidx.constraintlayout.widget.Guideline
@@ -223,6 +225,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        app:guidelineUseRtl="false"
         app:layout_constraintGuide_begin="406dp" />
 
     <androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 4d2310a..0bbe73c 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -27,7 +27,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="24dp"
+        android:layout_marginStart="24dp"
         android:ellipsize="end"
         android:maxLines="2"
         android:visibility="invisible"
@@ -41,7 +41,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="24dp"
+        android:layout_marginStart="24dp"
         android:text="@string/cancel"
         android:visibility="invisible"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -54,7 +54,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="24dp"
+        android:layout_marginStart="24dp"
         android:visibility="invisible"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent" />
@@ -66,7 +66,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginRight="24dp"
+        android:layout_marginEnd="24dp"
         android:ellipsize="end"
         android:maxLines="2"
         android:text="@string/biometric_dialog_confirm"
@@ -81,7 +81,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginRight="24dp"
+        android:layout_marginEnd="24dp"
         android:ellipsize="end"
         android:maxLines="2"
         android:text="@string/biometric_dialog_try_again"
diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml
deleted file mode 100644
index 8e885cf..0000000
--- a/packages/SystemUI/res/layout/contrast_dialog.xml
+++ /dev/null
@@ -1,127 +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.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal">
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_standard"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_standard"/>
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_standard"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
-        android:layout_height="match_parent" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_medium"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_medium"/>
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_medium"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
-        android:layout_height="match_parent" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_high"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_high"/>
-
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_high"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 19fb874..4234fca5 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -21,6 +21,19 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <ImageView
+        android:id="@+id/glanceable_hub_handle"
+        android:layout_width="4dp"
+        android:layout_height="220dp"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="12dp"
+        android:background="@drawable/hub_handle"
+        android:visibility="gone"
+        android:contentDescription="UI indicator for swiping open the glanceable hub"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/dream_overlay_content"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
similarity index 66%
rename from packages/SystemUI/res/layout/ongoing_call_chip.xml
rename to packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 6a0217ec..a33be12 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -17,43 +17,45 @@
      the chip. -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/ongoing_call_chip"
+    android:id="@+id/ongoing_activity_chip"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:layout_gravity="center_vertical|start"
     android:layout_marginStart="5dp"
 >
-    <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer
-        android:id="@+id/ongoing_call_chip_background"
+    <!-- TODO(b/332662551): Update this content description when this supports more than just
+         phone calls. -->
+    <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+        android:id="@+id/ongoing_activity_chip_background"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/ongoing_appops_chip_height"
         android:layout_gravity="center_vertical"
         android:gravity="center"
-        android:background="@drawable/ongoing_call_chip_bg"
-        android:paddingStart="@dimen/ongoing_call_chip_side_padding"
-        android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
+        android:background="@drawable/ongoing_activity_chip_bg"
+        android:paddingStart="@dimen/ongoing_activity_chip_side_padding"
+        android:paddingEnd="@dimen/ongoing_activity_chip_side_padding"
         android:contentDescription="@string/ongoing_phone_call_content_description"
         android:minWidth="@dimen/min_clickable_item_size"
     >
 
         <ImageView
             android:src="@*android:drawable/ic_phone"
-            android:layout_width="@dimen/ongoing_call_chip_icon_size"
-            android:layout_height="@dimen/ongoing_call_chip_icon_size"
+            android:layout_width="@dimen/ongoing_activity_chip_icon_size"
+            android:layout_height="@dimen/ongoing_activity_chip_icon_size"
             android:tint="?android:attr/colorPrimary"
         />
 
-        <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
-            android:id="@+id/ongoing_call_chip_time"
+        <com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+            android:id="@+id/ongoing_activity_chip_time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:gravity="center|start"
-            android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
+            android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
             android:textAppearance="@android:style/TextAppearance.Material.Small"
             android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textColor="?android:attr/colorPrimary"
         />
 
-    </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer>
+    </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 452bc31..4247c7e 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -99,7 +99,7 @@
                         android:gravity="center_vertical|start"
                     />
 
-                    <include layout="@layout/ongoing_call_chip" />
+                    <include layout="@layout/ongoing_activity_chip" />
 
                     <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                         android:id="@+id/notification_icon_area"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 873ddd0..9d0319c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1713,12 +1713,12 @@
     <dimen name="wallet_button_horizontal_padding">24dp</dimen>
     <dimen name="wallet_button_vertical_padding">8dp</dimen>
 
-    <!-- Ongoing call chip -->
-    <dimen name="ongoing_call_chip_side_padding">12dp</dimen>
-    <dimen name="ongoing_call_chip_icon_size">16dp</dimen>
+    <!-- Ongoing activity chip -->
+    <dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
+    <dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
     <!-- The padding between the icon and the text. -->
-    <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
-    <dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+    <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
+    <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
 
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
@@ -1963,15 +1963,6 @@
     <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
     <dimen name="broadcast_dialog_margin">16dp</dimen>
 
-    <!-- Contrast dialog -->
-    <dimen name="contrast_dialog_button_total_size">90dp</dimen>
-    <dimen name="contrast_dialog_button_inner_size">82dp</dimen>
-    <dimen name="contrast_dialog_button_radius">20dp</dimen>
-    <dimen name="contrast_dialog_button_stroke_width">4dp</dimen>
-    <dimen name="contrast_dialog_button_text_size">14sp</dimen>
-    <dimen name="contrast_dialog_button_text_spacing">4dp</dimen>
-    <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen>
-
     <!-- Shadow for dream overlay clock complication -->
     <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
     <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index aecc906..8da8316f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -902,15 +902,6 @@
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
 
-    <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_label">Contrast</string>
-    <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_standard">Standard</string>
-    <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_medium">Medium</string>
-    <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_high">High</string>
-
     <!-- Hearing devices -->
     <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] -->
     <string name="quick_settings_hearing_devices_label">Hearing devices</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2c4cdb9..393a1aa 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -523,10 +523,6 @@
         <item name="android:windowExitAnimation">@anim/instant_fade_out</item>
     </style>
 
-    <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
-        <item name="android:windowBackground">@android:color/transparent</item>
-    </style>
-
     <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
     </style>
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 40d38dd..6b61adc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -30,10 +30,10 @@
 import com.android.systemui.biometrics.shared.model.toSensorStrength
 import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-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.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
@@ -52,7 +53,7 @@
  */
 interface FingerprintPropertyRepository {
     /** Whether the fingerprint properties have been initialized yet. */
-    val propertiesInitialized: StateFlow<Boolean>
+    val propertiesInitialized: Flow<Boolean>
 
     /** The id of fingerprint sensor. */
     val sensorId: Flow<Int>
@@ -110,14 +111,8 @@
                 initialValue = UNINITIALIZED_PROPS,
             )
 
-    override val propertiesInitialized: StateFlow<Boolean> =
-        props
-            .map { it != UNINITIALIZED_PROPS }
-            .stateIn(
-                applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = props.value != UNINITIALIZED_PROPS,
-            )
+    override val propertiesInitialized: Flow<Boolean> =
+        props.map { it != UNINITIALIZED_PROPS }.onStart { emit(props.value != UNINITIALIZED_PROPS) }
 
     override val sensorId: Flow<Int> = props.map { it.sensorId }
 
@@ -141,7 +136,7 @@
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
-        private val UNINITIALIZED_PROPS =
+        val UNINITIALIZED_PROPS =
             FingerprintSensorPropertiesInternal(
                 -2 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 3112b67..d5b450d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -46,7 +46,7 @@
     displayStateInteractor: DisplayStateInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
-    val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+    val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized
     val isUdfps: StateFlow<Boolean> =
         repository.sensorType
             .map { it.isUdfps() }
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 f0969ed..13ea3f5 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
@@ -199,29 +199,32 @@
                                     iconParams.leftMargin = position.left
                                     mediumConstraintSet.clear(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END
+                                        ConstraintSet.RIGHT
                                     )
                                     mediumConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     mediumConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         position.left
                                     )
-                                    smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.END)
+                                    smallConstraintSet.clear(
+                                        R.id.biometric_icon,
+                                        ConstraintSet.RIGHT
+                                    )
                                     smallConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     smallConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         position.left
                                     )
                                 }
@@ -252,32 +255,32 @@
                                     iconParams.rightMargin = position.right
                                     mediumConstraintSet.clear(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     mediumConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.END
+                                        ConstraintSet.RIGHT
                                     )
                                     mediumConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         position.right
                                     )
                                     smallConstraintSet.clear(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     smallConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.END
+                                        ConstraintSet.RIGHT
                                     )
                                     smallConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         position.right
                                     )
                                 }
@@ -383,15 +386,15 @@
                                 // Move all content to other panel
                                 flipConstraintSet.connect(
                                     R.id.scrollView,
-                                    ConstraintSet.START,
+                                    ConstraintSet.LEFT,
                                     R.id.midGuideline,
-                                    ConstraintSet.START
+                                    ConstraintSet.LEFT
                                 )
                                 flipConstraintSet.connect(
                                     R.id.scrollView,
-                                    ConstraintSet.END,
+                                    ConstraintSet.RIGHT,
                                     R.id.rightGuideline,
-                                    ConstraintSet.END
+                                    ConstraintSet.RIGHT
                                 )
                             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index f0230be..911145b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -419,8 +419,7 @@
         const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
             "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
-        const val ACTION_AUDIO_SHARING =
-            "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
+        const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
         const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
index c018ecb..0544a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -66,7 +68,18 @@
                         user.id
                     )
                     ?.let { PolicyRestriction.Restricted(it) }
-                    ?: PolicyRestriction.NoRestriction
+                    ?: if (
+                        enforceBrightnessBaseUserRestriction() &&
+                            userRestrictionChecker.hasBaseUserRestriction(
+                                applicationContext,
+                                UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+                                user.id
+                            )
+                    ) {
+                        PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+                    } else {
+                        PolicyRestriction.NoRestriction
+                    }
             }
             .flowOn(backgroundDispatcher)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 656e5cb..97db43b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
@@ -63,6 +64,7 @@
     @Application private val scope: CoroutineScope,
     @Main private val resources: Resources,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     private val shadeInteractor: ShadeInteractor,
@@ -236,6 +238,14 @@
      */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
+    // TODO(b/339667383): remove this temporary swipe gesture handle
+    /**
+     * The dream overlay has its own gesture handle as the SysUI window is not visible above the
+     * dream. This flow will be false when dreaming so that we don't show a duplicate handle when
+     * opening the hub over the dream.
+     */
+    val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
+
     companion object {
         const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
     }
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
deleted file mode 100644
index 4e40042..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.Activity
-import android.os.Bundle
-import javax.inject.Inject
-
-/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */
-class ContrastDialogActivity
-@Inject
-constructor(
-    private val contrastDialogDelegate : ContrastDialogDelegate
-) : Activity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        contrastDialogDelegate.createDialog().show()
-        finish()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
deleted file mode 100644
index 0daa058..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.app.UiModeManager.ContrastUtils.toContrastLevel
-import android.os.Bundle
-import android.provider.Settings
-import android.view.View
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.settings.SecureSettings
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/** Dialog to select contrast options */
-class ContrastDialogDelegate
-@Inject
-constructor(
-    private val sysuiDialogFactory: SystemUIDialog.Factory,
-    @Main private val mainExecutor: Executor,
-    private val uiModeManager: UiModeManager,
-    private val userTracker: UserTracker,
-    private val secureSettings: SecureSettings,
-) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener {
-
-    @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
-    lateinit var dialogView: View
-    @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
-
-    override fun createDialog(): SystemUIDialog {
-        val dialog = sysuiDialogFactory.create(this)
-        dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null)
-        with(dialog) {
-            setView(dialogView)
-
-            setTitle(R.string.quick_settings_contrast_label)
-            setNeutralButton(R.string.cancel) { _, _ ->
-                secureSettings.putFloatForUser(
-                    Settings.Secure.CONTRAST_LEVEL,
-                    initialContrast,
-                    userTracker.userId
-                )
-                dialog.dismiss()
-            }
-            setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() }
-        }
-
-        return dialog
-    }
-
-    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
-        contrastButtons =
-            mapOf(
-                CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high)
-            )
-
-        contrastButtons.forEach { (contrastLevel, contrastButton) ->
-            contrastButton.setOnClickListener {
-                val contrastValue = fromContrastLevel(contrastLevel)
-                secureSettings.putFloatForUser(
-                    Settings.Secure.CONTRAST_LEVEL,
-                    contrastValue,
-                    userTracker.userId
-                )
-            }
-        }
-
-        initialContrast = uiModeManager.contrast
-        highlightContrast(toContrastLevel(initialContrast))
-    }
-
-    override fun onStart(dialog: SystemUIDialog) {
-        uiModeManager.addContrastChangeListener(mainExecutor, this)
-    }
-
-    override fun onStop(dialog: SystemUIDialog) {
-        uiModeManager.removeContrastChangeListener(this)
-    }
-
-    override fun onContrastChanged(contrast: Float) {
-        highlightContrast(toContrastLevel(contrast))
-    }
-
-    private fun highlightContrast(contrast: Int) {
-        contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index d2df276..c2e1e33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.communal.widgets.EditWidgetsActivity;
-import com.android.systemui.contrast.ContrastDialogActivity;
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -72,12 +71,6 @@
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
 
-    /** Inject into ContrastDialogActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(ContrastDialogActivity.class)
-    public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity);
-
     /** Inject into UsbDebuggingActivity. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 60006c6..1e725eb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -21,6 +21,8 @@
 import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
+import static com.android.systemui.Flags.communalHub;
+import static com.android.systemui.Flags.glanceableHubGestureHandle;
 import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
 import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
@@ -185,6 +187,7 @@
             DreamOverlayContainerView containerView,
             ComplicationHostViewController complicationHostViewController,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
+            @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView,
             DreamOverlayStatusBarViewController statusBarViewController,
             LowLightTransitionCoordinator lowLightTransitionCoordinator,
             BlurUtils blurUtils,
@@ -220,6 +223,12 @@
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
                 R.dimen.dream_overlay_y_offset);
+
+        if (communalHub() && glanceableHubGestureHandle()) {
+            // TODO(b/339667383): remove this temporary swipe gesture handle
+            hubGestureIndicatorView.setVisibility(View.VISIBLE);
+        }
+
         final View view = mComplicationHostViewController.getView();
 
         mDreamOverlayContentView.addView(view,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 999e681..789b7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.lifecycle.Lifecycle;
@@ -39,6 +40,7 @@
 @Module
 public abstract class DreamOverlayModule {
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
+    public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
             "burn_in_protection_update_interval";
@@ -71,6 +73,18 @@
                 "R.id.dream_overlay_content must not be null");
     }
 
+    /**
+     * Gesture indicator bar on the right edge of the screen to indicate to users that they can
+     * swipe to see their widgets on lock screen.
+     */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    @Named(HUB_GESTURE_INDICATOR_VIEW)
+    public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle),
+                "R.id.glanceable_hub_handle must not be null");
+    }
+
     /** */
     @Provides
     public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 857096e..b1ef76e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Context
+import android.util.Log
 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -42,6 +43,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -78,7 +80,15 @@
     private val refreshEvents: Flow<Unit> =
         merge(
             configurationInteractor.onAnyConfigurationChange,
-            fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map { Unit },
+            fingerprintPropertyInteractor.propertiesInitialized
+                .filter { it }
+                .map { Unit }
+                .onEach {
+                    Log.d(
+                        "KeyguardBlueprintInteractor",
+                        "triggering refreshEvent from fpPropertiesInitialized"
+                    )
+                },
         )
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 8065c0f..c44a40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -55,6 +55,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -181,7 +182,11 @@
             }
             .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
             .debounce(50L)
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
 
     /** Whether the keyguard is showing or not. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
@@ -225,7 +230,19 @@
     @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
 
     /** Whether the alternate bouncer is showing or not. */
-    val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+    val alternateBouncerShowing: Flow<Boolean> =
+        bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) {
+            alternateBouncerVisible,
+            isAbleToDream ->
+            if (isAbleToDream) {
+                // If the alternate bouncer will show over a dream, it is likely that the dream has
+                // requested a dismissal, which will stop the dream. By delaying this slightly, the
+                // DREAMING->LOCKSCREEN transition will now happen first, followed by
+                // LOCKSCREEN->ALTERNATE_BOUNCER.
+                delay(600L)
+            }
+            alternateBouncerVisible
+        }
 
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
@@ -301,10 +318,12 @@
                     shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
                     keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
                 ) { legacyShadeExpansion, goneValue ->
-                    if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) {
+                    val isLegacyShadeInResetPosition =
+                        legacyShadeExpansion == 0f || legacyShadeExpansion == 1f
+                    if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) {
                         // Reset the translation value
                         emit(0f)
-                    } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) {
+                    } else if (!isLegacyShadeInResetPosition) {
                         // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
                         // transition is running, which means this is a swipe to dismiss. Values of
                         // 0f and 1f need to be ignored in the legacy shade expansion. These can
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 229e592..19e3e07 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
@@ -308,7 +308,11 @@
      * It will be called when the container is out of view.
      */
     lateinit var updateUserVisibility: () -> Unit
-    lateinit var updateHostVisibility: () -> Unit
+    var updateHostVisibility: () -> Unit = {}
+        set(value) {
+            field = value
+            mediaCarouselViewModel.updateHostVisibility = value
+        }
 
     private val isReorderingAllowed: Boolean
         get() = visualStabilityProvider.isReorderingAllowed
@@ -345,6 +349,20 @@
         configurationController.addCallback(configListener)
         if (!mediaFlags.isMediaControlsRefactorEnabled()) {
             setUpListeners()
+        } else {
+            val visualStabilityCallback = OnReorderingAllowedListener {
+                mediaCarouselViewModel.onReorderingAllowed()
+
+                // Update user visibility so that no extra impression will be logged when
+                // activeMediaIndex resets to 0
+                if (this::updateUserVisibility.isInitialized) {
+                    updateUserVisibility()
+                }
+
+                // Let's reset our scroll position
+                mediaCarouselScrollHandler.scrollToStart()
+            }
+            visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
         }
         mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
             // The pageIndicator is not laid out yet when we get the current state update,
@@ -366,10 +384,6 @@
         )
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
         mediaCarousel.repeatWhenAttached {
-            if (mediaFlags.isMediaControlsRefactorEnabled()) {
-                mediaCarouselViewModel.onAttached()
-                mediaCarouselScrollHandler.scrollToStart()
-            }
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 listenForAnyStateToGoneKeyguardTransition(this)
                 listenForAnyStateToLockscreenTransition(this)
@@ -592,9 +606,7 @@
                         if (!immediately) {
                             // Although it wasn't requested, we were able to process the removal
                             // immediately since reordering is allowed. So, notify hosts to update
-                            if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
-                                updateHostVisibility()
-                            }
+                            updateHostVisibility()
                         }
                     } else {
                         keysNeedRemoval.add(key)
@@ -751,6 +763,7 @@
             }
         }
         viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+        controllerByViewModel[commonViewModel] = viewController
         updateViewControllerToState(viewController, noAnimation = true)
         updatePageIndicator()
         if (
@@ -764,7 +777,6 @@
         mediaCarouselScrollHandler.onPlayersChanged()
         mediaFrame.requiresRemeasuring = true
         commonViewModel.onAdded(commonViewModel)
-        controllerByViewModel[commonViewModel] = viewController
     }
 
     private fun onUpdated(commonViewModel: MediaCommonViewModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index fd5f445..4e90936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -57,12 +57,12 @@
     val mediaItems: StateFlow<List<MediaCommonViewModel>> =
         interactor.currentMedia
             .map { sortedItems ->
-                buildList {
+                val mediaList = buildList {
                     sortedItems.forEach { commonModel ->
                         // When view is started we should make sure to clean models that are pending
                         // removal.
                         // This action should only be triggered once.
-                        if (!isAttached || !modelsPendingRemoval.contains(commonModel)) {
+                        if (!allowReorder || !modelsPendingRemoval.contains(commonModel)) {
                             when (commonModel) {
                                 is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
                                 is MediaCommonModel.MediaRecommendations ->
@@ -70,11 +70,16 @@
                             }
                         }
                     }
-                    if (isAttached) {
-                        modelsPendingRemoval.clear()
-                    }
-                    isAttached = false
                 }
+                if (allowReorder) {
+                    if (modelsPendingRemoval.size > 0) {
+                        updateHostVisibility()
+                    }
+                    modelsPendingRemoval.clear()
+                }
+                allowReorder = false
+
+                mediaList
             }
             .stateIn(
                 scope = applicationScope,
@@ -82,6 +87,8 @@
                 initialValue = emptyList(),
             )
 
+    var updateHostVisibility: () -> Unit = {}
+
     private val mediaControlByInstanceId =
         mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
 
@@ -89,15 +96,15 @@
 
     private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
 
-    private var isAttached = false
+    private var allowReorder = false
 
     fun onSwipeToDismiss() {
         logger.logSwipeDismiss()
         interactor.onSwipeToDismiss()
     }
 
-    fun onAttached() {
-        isAttached = true
+    fun onReorderingAllowed() {
+        allowReorder = true
         interactor.reorderMedia()
     }
 
@@ -194,7 +201,11 @@
     ) {
         if (immediatelyRemove || isReorderingAllowed()) {
             interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
-            // TODO if not immediate remove update host visibility
+            if (!immediatelyRemove) {
+                // Although it wasn't requested, we were able to process the removal
+                // immediately since reordering is allowed. So, notify hosts to update
+                updateHostVisibility()
+            }
         } else {
             modelsPendingRemoval.add(commonModel)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3907a72..5e6ee4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -16,12 +16,20 @@
 
 package com.android.systemui.qrcodescanner.dagger
 
+import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
 import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
@@ -54,5 +62,24 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
+
+        /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
+        @Provides
+        @IntoMap
+        @StringKey(QR_CODE_SCANNER_TILE_SPEC)
+        fun provideQRCodeScannerTileViewModel(
+            factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>,
+            mapper: QRCodeScannerTileMapper,
+            stateInteractor: QRCodeScannerTileDataInteractor,
+            userActionInteractor: QRCodeScannerTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(QR_CODE_SCANNER_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
index 2c8a5a4..1336d64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -18,6 +18,7 @@
 
 import android.service.quicksettings.Tile
 import android.text.TextUtils
+import android.widget.Switch
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.external.CustomTile
 import com.android.systemui.qs.nano.QsTileState
@@ -44,8 +45,8 @@
         }
     label?.let { state.label = it.toString() }
     secondaryLabel?.let { state.secondaryLabel = it.toString() }
-    if (this is QSTile.BooleanState) {
-        state.booleanState = value
+    if (expandedAccessibilityClassName == Switch::class.java.name) {
+        state.booleanState = state.state == QsTileState.ACTIVE
     }
     return state
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d26ae0a..5d35a69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -42,6 +42,7 @@
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
+import android.widget.Button;
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
@@ -502,6 +503,8 @@
         if (state instanceof BooleanState) {
             state.expandedAccessibilityClassName = Switch.class.getName();
             ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+        } else {
+            state.expandedAccessibilityClassName = Button.class.getName();
         }
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 2068799..71b69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,9 +34,12 @@
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -54,6 +59,8 @@
 
 import dagger.Lazy;
 
+import kotlinx.coroutines.Job;
+
 import javax.inject.Inject;
 
 /** Quick settings tile: Airplane mode **/
@@ -66,6 +73,9 @@
     private final Lazy<ConnectivityManager> mLazyConnectivityManager;
 
     private boolean mListening;
+    @Nullable
+    @VisibleForTesting
+    Job mClickJob;
 
     @Inject
     public AirplaneModeTile(
@@ -111,6 +121,21 @@
                     new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
             return;
         }
+
+        if (Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> {
+                        if (isAllowClick) {
+                            setEnabled(!airplaneModeEnabled);
+                        }
+                        return null;
+                    });
+            return;
+        }
+
         setEnabled(!airplaneModeEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9af34f6..9f41d98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH;
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.annotation.Nullable;
@@ -33,11 +34,14 @@
 import android.util.Log;
 import android.widget.Switch;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -55,6 +59,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
+import kotlinx.coroutines.Job;
+
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -78,6 +84,9 @@
     private final BluetoothTileDialogViewModel mDialogViewModel;
 
     private final FeatureFlags mFeatureFlags;
+    @Nullable
+    @VisibleForTesting
+    Job mClickJob;
 
     @Inject
     public BluetoothTile(
@@ -110,6 +119,24 @@
 
     @Override
     protected void handleClick(@Nullable Expandable expandable) {
+        if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> {
+                        if (!isAllowClick) {
+                            return null;
+                        }
+                        handleClickEvent(expandable);
+                        return null;
+                    });
+            return;
+        }
+        handleClickEvent(expandable);
+    }
+
+    private void handleClickEvent(@Nullable Expandable expandable) {
         if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
             mDialogViewModel.showDialog(expandable);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 2d3120a..972b20e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -29,11 +29,15 @@
 
 /**
  * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
- * dismissing and tile from-view animations.
+ * dismissing and tile from-view animations, as well as the option to show over lockscreen.
  */
 interface QSTileIntentUserInputHandler {
 
-    fun handle(expandable: Expandable?, intent: Intent)
+    fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        dismissShadeShowOverLockScreenWhenLocked: Boolean = false
+    )
 
     /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
     fun handle(
@@ -52,12 +56,25 @@
     private val userHandle: UserHandle,
 ) : QSTileIntentUserInputHandler {
 
-    override fun handle(expandable: Expandable?, intent: Intent) {
+    override fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        dismissShadeShowOverLockScreenWhenLocked: Boolean
+    ) {
         val animationController: ActivityTransitionAnimator.Controller? =
             expandable?.activityTransitionController(
                 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
             )
-        activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+        if (dismissShadeShowOverLockScreenWhenLocked) {
+            activityStarter.startActivity(
+                intent,
+                true /* dismissShade */,
+                animationController,
+                true /* showOverLockscreenWhenLocked */
+            )
+        } else {
+            activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+        }
     }
 
     // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index c9c4443..c971f54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.tiles.dialog;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI;
 import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
 
@@ -57,6 +58,8 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -73,6 +76,7 @@
 import dagger.assisted.AssistedInject;
 
 import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -161,6 +165,9 @@
     // Wi-Fi scanning progress bar
     protected boolean mIsProgressBarVisible;
     private SystemUIDialog mDialog;
+    private final CoroutineScope mCoroutineScope;
+    @Nullable
+    private Job mClickJob;
 
     @AssistedFactory
     public interface Factory {
@@ -203,7 +210,7 @@
         mCanConfigWifi = canConfigWifi;
         mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
         mKeyguard = keyguardStateController;
-
+        mCoroutineScope = coroutineScope;
         mUiEventLogger = uiEventLogger;
         mDialogTransitionAnimator = dialogTransitionAnimator;
         mAdapter = new InternetAdapter(mInternetDialogController, coroutineScope);
@@ -388,11 +395,9 @@
         });
         mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
         mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
-        mWiFiToggle.setOnCheckedChangeListener(
-                (buttonView, isChecked) -> {
-                    if (mInternetDialogController.isWifiEnabled() == isChecked) return;
-                    mInternetDialogController.setWifiEnabled(isChecked);
-                });
+        mWiFiToggle.setOnClickListener(v -> {
+            handleWifiToggleClicked(mWiFiToggle.isChecked());
+        });
         mDoneButton.setOnClickListener(v -> dialog.dismiss());
         mShareWifiButton.setOnClickListener(v -> {
             if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry, v)) {
@@ -404,6 +409,32 @@
         });
     }
 
+    private void handleWifiToggleClicked(boolean isChecked) {
+        if (Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> {
+                        if (isAllowClick) {
+                            setWifiEnable(isChecked);
+                        } else {
+                            mWiFiToggle.setChecked(!isChecked);
+                        }
+                        return null;
+                    });
+            return;
+        }
+        setWifiEnable(isChecked);
+    }
+
+    private void setWifiEnable(boolean isChecked) {
+        if (mInternetDialogController.isWifiEnabled() == isChecked) {
+            return;
+        }
+        mInternetDialogController.setWifiEnabled(isChecked);
+    }
+
     @MainThread
     private void updateEthernet() {
         mEthernetLayout.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
new file mode 100644
index 0000000..1e8ce58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */
+class QRCodeScannerTileDataInteractor
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val qrController: QRCodeScannerController,
+) : QSTileDataInteractor<QRCodeScannerTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<QRCodeScannerTileModel> =
+        conflatedCallbackFlow {
+                qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE)
+                val callback =
+                    object : QRCodeScannerController.Callback {
+                        override fun onQRCodeScannerActivityChanged() {
+                            trySend(generateModel())
+                        }
+                    }
+                qrController.addCallback(callback)
+                awaitClose {
+                    qrController.removeCallback(callback)
+                    qrController.unregisterQRCodeScannerChangeObservers(
+                        DEFAULT_QR_CODE_SCANNER_CHANGE
+                    )
+                }
+            }
+            .onStart { emit(generateModel()) }
+            .flowOn(bgCoroutineContext)
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                QRCodeScannerTileModel.TemporarilyUnavailable
+            )
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(qrController.isCameraAvailable)
+
+    private fun generateModel(): QRCodeScannerTileModel {
+        val intent = qrController.intent
+
+        return if (qrController.isAbleToLaunchScannerActivity && intent != null)
+            QRCodeScannerTileModel.Available(intent)
+        else QRCodeScannerTileModel.TemporarilyUnavailable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
new file mode 100644
index 0000000..7c0c41e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.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.qs.tiles.impl.qr.domain.interactor
+
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles qr tile clicks. */
+class QRCodeScannerTileUserActionInteractor
+@Inject
+constructor(
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<QRCodeScannerTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    when (data) {
+                        is QRCodeScannerTileModel.Available ->
+                            qsTileIntentUserActionHandler.handle(
+                                action.expandable,
+                                data.intent,
+                                true
+                            )
+                        is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op
+                    }
+                }
+                is QSTileUserAction.LongClick -> {} // no-op
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
new file mode 100644
index 0000000..22c9b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.model
+
+import android.content.Intent
+
+/** qr scanner tile model. */
+sealed interface QRCodeScannerTileModel {
+    data class Available(val intent: Intent) : QRCodeScannerTileModel
+    data object TemporarilyUnavailable : QRCodeScannerTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
new file mode 100644
index 0000000..45a7717
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
+class QRCodeScannerTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+
+    override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.qr_code_scanner_title)
+            contentDescription = label
+            icon = {
+                Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
+            }
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+            when (data) {
+                is QRCodeScannerTileModel.Available -> {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    secondaryLabel = null
+                }
+                is QRCodeScannerTileModel.TemporarilyUnavailable -> {
+                    activationState = QSTileState.ActivationState.UNAVAILABLE
+                    secondaryLabel =
+                        resources.getString(R.string.qr_code_scanner_updating_secondary_label)
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index 5e561cf..ee1944e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -45,6 +45,7 @@
 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 
 import java.util.List;
@@ -378,8 +379,14 @@
                 upper = 1;
                 break;
         }
-        Log.i(TAG, "getAllowedValues: " + boundary + ", "
-                + "result=[lower=" + lower + ", upper=" + upper + "]");
+        if (lower >= upper) {
+            Log.wtf(TAG, "getAllowedValues computed an invalid range "
+                    + "[" + lower + ", " + upper + "]");
+            if (Flags.screenshotScrollCropViewCrashFix()) {
+                lower = Math.min(lower, upper);
+                upper = lower;
+            }
+        }
         return new Range<>(lower, upper);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
index ce88a5f..cae86a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.content.Context
 import android.util.AttributeSet
 import com.android.systemui.animation.view.LaunchableLinearLayout
 
 /**
- * A container view for the ongoing call chip background. Needed so that we can limit the height of
- * the background when the font size is very large (200%), in which case the background would go
+ * A container view for the ongoing activity chip background. Needed so that we can limit the height
+ * of the background when the font size is very large (200%), in which case the background would go
  * past the bounds of the status bar.
  */
-class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) :
+class ChipBackgroundContainer(context: Context, attrs: AttributeSet) :
     LaunchableLinearLayout(context, attrs) {
 
     /** Sets where this view should fetch its max height from. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
index bb7ba4c..ff3061e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
@@ -14,36 +14,34 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.content.Context
 import android.util.AttributeSet
-
 import android.widget.Chronometer
 import androidx.annotation.UiThread
 
 /**
- * A [Chronometer] specifically for the ongoing call chip in the status bar.
+ * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an
+ * activity.
  *
  * This class handles:
- *   1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would
- *      change slightly each second because the width of each number is slightly different.
+ * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change
+ *    slightly each second because the width of each number is slightly different.
  *
- *      Instead, we save the largest number width seen so far and ensure that the chip is at least
- *      that wide. This means the chip may get larger over time (e.g. in the transition from 59:59
- *      to 1:00:00), but never smaller.
- *
- *   2) Hiding the text if the time gets too long for the space available. Once the text has been
- *      hidden, it remains hidden for the duration of the call.
+ *    Instead, we save the largest number width seen so far and ensure that the chip is at least
+ *    that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
+ *    1:00:00), but never smaller.
+ * 2) Hiding the text if the time gets too long for the space available. Once the text has been
+ *    hidden, it remains hidden for the duration of the activity.
  *
  * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
  * text will also be hidden in landscape (even if there is enough space for it in landscape).
  */
-class OngoingCallChronometer @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyle: Int = 0
-) : Chronometer(context, attrs, defStyle) {
+class ChipChronometer
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+    Chronometer(context, attrs, defStyle) {
 
     // Minimum width that the text view can be. Corresponds with the largest number width seen so
     // far.
@@ -53,8 +51,8 @@
     private var shouldHideText: Boolean = false
 
     override fun setBase(base: Long) {
-        // These variables may have changed during the previous call, so re-set them before the new
-        // call starts.
+        // These variables may have changed during the previous activity, so re-set them before the
+        // new activity starts.
         minimumTextWidth = 0
         shouldHideText = false
         visibility = VISIBLE
@@ -75,9 +73,7 @@
         }
 
         // Evaluate how wide the text *wants* to be if it had unlimited space.
-        super.onMeasure(
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                heightMeasureSpec)
+        super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec)
         val desiredTextWidth = measuredWidth
 
         // Evaluate how wide the text *can* be based on the enforced constraints
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 968b591..5a616df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,7 +18,7 @@
 
 import static android.graphics.PorterDuff.Mode.SRC_ATOP;
 
-import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization;
 import static com.android.systemui.util.ColorUtilKt.hexColorString;
 
 import android.annotation.ColorInt;
@@ -407,7 +407,7 @@
         final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final @ColorInt int scHigh;
-        if (!notificationBackgroundTintOptimization()) {
+        if (!notificationFooterBackgroundTintOptimization()) {
             scHigh = Utils.getColorAttrDefaultColor(mContext,
                     com.android.internal.R.attr.materialColorSurfaceContainerHigh);
             if (scHigh != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index db3cf5a..9d0fcd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.app.Flags
+import android.os.SystemProperties
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import javax.inject.Inject
 
@@ -34,8 +35,13 @@
     HeadsUpStyleProvider {
 
     override fun shouldApplyCompactStyle(): Boolean {
-        // Use compact HUN for immersive mode.
-        return Flags.compactHeadsUpNotification() &&
-            statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+        return Flags.compactHeadsUpNotification() && (isInImmersiveMode() || alwaysShow())
     }
+
+    private fun isInImmersiveMode() =
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+
+    /** developer setting to always show Minimal HUN, even if the device is not in full screen */
+    private fun alwaysShow() =
+        SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index abbe7d7..17b54c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -674,7 +674,7 @@
     void setOverExpansion(float margin) {
         mAmbientState.setOverExpansion(margin);
         if (notificationOverExpansionClippingFix() && !SceneContainerFlag.isEnabled()) {
-            setRoundingClippingYTranslation((int) margin);
+            setRoundingClippingYTranslation(mShouldUseSplitNotificationShade ? (int) margin : 0);
         }
         updateStackPosition();
         requestChildrenUpdate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5b0b46e..d3d2b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2805,7 +2805,16 @@
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+            if (DeviceEntryUdfpsRefactor.isEnabled()) {
+                if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
+                        && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
+                        || mTransitionToFullShadeProgress > 0f)) {
+                    // Assume scrim state for shade is already correct and do nothing
+                } else {
+                    // Safeguard which prevents the scrim from being stuck in the wrong state
+                    mScrimController.transitionTo(ScrimState.KEYGUARD);
+                }
+            } else {
                 if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
                         && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
                         || mTransitionToFullShadeProgress > 0f)) {
@@ -2814,7 +2823,6 @@
                     mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
                 }
             }
-
             // This will cancel the keyguardFadingAway animation if it is running. We need to do
             // this as otherwise it can remain pending and leave keyguard in a weird state.
             mUnlockScrimCallback.onCancelled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 4fc11df..a858fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -119,7 +119,7 @@
     private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
-    private View mOngoingCallChip;
+    private View mOngoingActivityChip;
     private View mNotificationIconAreaInner;
     // Visibilities come in from external system callers via disable flags, but we also sometimes
     // modify the visibilities internally. We need to store both so that we don't accidentally
@@ -345,7 +345,7 @@
         mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
         mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
         mClockView = mStatusBar.findViewById(R.id.clock);
-        mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
+        mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip);
         showEndSideContent(false);
         showClock(false);
         initOperatorName();
@@ -594,9 +594,9 @@
         // so if the icons are disabled then the call chip should be, too.)
         boolean showOngoingCallChip = hasOngoingCall && !disableNotifications;
         if (showOngoingCallChip) {
-            showOngoingCallChip(animate);
+            showOngoingActivityChip(animate);
         } else {
-            hideOngoingCallChip(animate);
+            hideOngoingActivityChip(animate);
         }
         mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip);
     }
@@ -688,14 +688,19 @@
         animateShow(mClockView, animate);
     }
 
-    /** Hides the ongoing call chip. */
-    public void hideOngoingCallChip(boolean animate) {
-        animateHiddenState(mOngoingCallChip, View.GONE, animate);
+    /** Hides the ongoing activity chip. */
+    private void hideOngoingActivityChip(boolean animate) {
+        animateHiddenState(mOngoingActivityChip, View.GONE, animate);
     }
 
-    /** Displays the ongoing call chip. */
-    public void showOngoingCallChip(boolean animate) {
-        animateShow(mOngoingCallChip, animate);
+    /**
+     * Displays the ongoing activity chip.
+     *
+     * For now, this chip will only ever contain the ongoing call information, but after b/332662551
+     * feature is implemented, it will support different kinds of ongoing activities.
+     */
+    private void showOngoingActivityChip(boolean animate) {
+        animateShow(mOngoingActivityChip, animate);
     }
 
     /**
@@ -803,7 +808,7 @@
 
     private void initOngoingCallChip() {
         mOngoingCallController.addCallback(mOngoingCallListener);
-        mOngoingCallController.setChipView(mOngoingCallChip);
+        mOngoingCallController.setChipView(mOngoingActivityChip);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index ec88b6c..a7d4ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -36,6 +36,8 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -145,8 +147,8 @@
     fun setChipView(chipView: View) {
         tearDownChipView()
         this.chipView = chipView
-        val backgroundView: OngoingCallBackgroundContainer? =
-            chipView.findViewById(R.id.ongoing_call_chip_background)
+        val backgroundView: ChipBackgroundContainer? =
+            chipView.findViewById(R.id.ongoing_activity_chip_background)
         backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
         if (hasOngoingCall()) {
             updateChip()
@@ -226,7 +228,7 @@
         if (callNotificationInfo == null) { return }
         val currentChipView = chipView
         val backgroundView =
-            currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+            currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
         val intent = callNotificationInfo?.intent
         if (currentChipView != null && backgroundView != null && intent != null) {
             currentChipView.setOnClickListener {
@@ -271,8 +273,8 @@
     @VisibleForTesting
     fun tearDownChipView() = chipView?.getTimeView()?.stop()
 
-    private fun View.getTimeView(): OngoingCallChronometer? {
-        return this.findViewById(R.id.ongoing_call_chip_time)
+    private fun View.getTimeView(): ChipChronometer? {
+        return this.findViewById(R.id.ongoing_activity_chip_time)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 9c78ab4..886481e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
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 f44e7f3..8b48bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import com.android.systemui.util.Compile
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -37,7 +38,7 @@
 ) : Dumpable {
 
     private val tag = "AvalancheController"
-    private val debug = false
+    private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
 
     // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
     @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -108,7 +109,10 @@
             if (isOnlyNextEntry) {
                 // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
                 // and goes to the isShowing case above
-                headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+                headsUpEntryShowing!!.updateEntry(
+                        /* updatePostTime= */ false,
+                        /* updateEarliestRemovalTime= */ false,
+                        /* reason= */ "avalanche duration update")
             }
         }
         logState("after $fn")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index b8318a7..a7fe49b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -39,6 +39,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -114,7 +115,8 @@
         mUiEventLogger = uiEventLogger;
         mAvalancheController = avalancheController;
         Resources resources = context.getResources();
-        mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+        mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
+                ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mStickyForSomeTimeAutoDismissTime = resources.getInteger(
                 R.integer.sticky_heads_up_notification_time);
         mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
@@ -765,11 +767,23 @@
          * @param updatePostTime whether or not to refresh the post time
          */
         public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+            updateEntry(updatePostTime, /* updateEarliestRemovalTime= */ true, reason);
+        }
+
+        /**
+         * Updates an entry's removal time.
+         * @param updatePostTime whether or not to refresh the post time
+         * @param updateEarliestRemovalTime whether this update should further delay removal
+         */
+        public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime,
+                @Nullable String reason) {
             Runnable runnable = () -> {
                 mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
 
                 final long now = mSystemClock.elapsedRealtime();
-                mEarliestRemovalTime = now + mMinimumDisplayTime;
+                if (updateEarliestRemovalTime) {
+                    mEarliestRemovalTime = now + mMinimumDisplayTime;
+                }
 
                 if (updatePostTime) {
                     mPostTime = Math.max(mPostTime, now);
@@ -785,7 +799,9 @@
             FinishTimeUpdater finishTimeCalculator = () -> {
                 final long finishTime = calculateFinishTime();
                 final long now = mSystemClock.elapsedRealtime();
-                final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+                final long timeLeft = NotificationThrottleHun.isEnabled()
+                        ? Math.max(finishTime, mEarliestRemovalTime) - now
+                        : Math.max(finishTime - now, mMinimumDisplayTime);
                 return timeLeft;
             };
             scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -818,13 +834,6 @@
         }
 
         public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
-            boolean isPinned = mEntry.isRowPinned();
-            boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
-            if (isPinned && !otherPinned) {
-                return -1;
-            } else if (!isPinned && otherPinned) {
-                return 1;
-            }
             boolean selfFullscreen = hasFullScreenIntent(mEntry);
             boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry);
             if (selfFullscreen && !otherFullscreen) {
@@ -851,6 +860,13 @@
         }
 
         public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+            boolean isPinned = mEntry.isRowPinned();
+            boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
+            if (isPinned && !otherPinned) {
+                return -1;
+            } else if (!isPinned && otherPinned) {
+                return 1;
+            }
             int nonTimeCompareResult = compareNonTimeFields(headsUpEntry);
             if (nonTimeCompareResult != 0) {
                 return nonTimeCompareResult;
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 9339651..516cb46 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -533,7 +533,7 @@
                 targetUserId = targetUserId,
                 ::showDialog,
                 ::dismissDialog,
-                ::selectUser,
+                ::switchUser
             )
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 51ceda3..f9fe5e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.KeyEvent;
@@ -125,8 +126,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
     public void withFeatureFlagOn_oldMessage_isHidden() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
         KeyguardAbsKeyInputViewController underTest = createTestObject();
 
         underTest.init();
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 97c3c42..fa78f0c 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
@@ -37,6 +37,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.platform.test.annotations.EnableFlags
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
@@ -1272,8 +1273,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_BP_TALKBACK)
     fun hint_for_talkback_guidance() = runGenericTest {
-        mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK)
         val hint by collectLastValue(viewModel.accessibilityHint)
 
         // Touches should fall outside of sensor area
@@ -1295,10 +1296,9 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionOverriddenByVerticalListContentView() =
         runGenericTest(contentView = promptContentView, description = "test description") {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1307,13 +1307,12 @@
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
         runGenericTest(
             contentView = promptContentViewWithMoreOptionsButton,
             description = "test description"
         ) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1322,10 +1321,9 @@
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionWithoutContentView() =
         runGenericTest(description = "test description") {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1334,19 +1332,17 @@
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_nullIfPkgNameNotFound() =
         runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isNull()
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_defaultWithOverrides() =
         runGenericTest(packageName = packageNameForLogoWithOverrides) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
 
             // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
@@ -1357,71 +1353,63 @@
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_defaultIsNull() =
         runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isNull()
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_default() = runGenericTest {
-        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         val logo by collectLastValue(viewModel.logo)
         assertThat(logo).isEqualTo(defaultLogoIcon)
     }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_resSetByApp() =
         runGenericTest(logoRes = logoResFromApp) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isEqualTo(logoFromApp)
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_bitmapSetByApp() =
         runGenericTest(logoBitmap = logoBitmapFromApp) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logoDescription_emptyIfPkgNameNotFound() =
         runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo("")
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logoDescription_defaultIsEmpty() =
         runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo("")
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logoDescription_default() = runGenericTest {
-        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         val logoDescription by collectLastValue(viewModel.logoDescription)
         assertThat(logoDescription).isEqualTo(defaultLogoDescription)
     }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logoDescription_setByApp() =
         runGenericTest(logoDescription = logoDescriptionFromApp) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index f62a55d..11f74c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -63,6 +64,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
 class BluetoothTileDialogViewModelTest : SysuiTestCase() {
 
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -113,7 +115,6 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
         scheduler = TestCoroutineScheduler()
         dispatcher = UnconfinedTestDispatcher(scheduler)
         testScope = TestScope(dispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
deleted file mode 100644
index ab03465..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.os.Looper
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.model.SysUiState
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class ContrastDialogDelegateTest : SysuiTestCase() {
-
-    private val mainExecutor = FakeExecutor(FakeSystemClock())
-    private lateinit var mContrastDialogDelegate: ContrastDialogDelegate
-    @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
-    @Mock private lateinit var sysuiDialog: SystemUIDialog
-    @Mock private lateinit var mockUiModeManager: UiModeManager
-    @Mock private lateinit var mockUserTracker: UserTracker
-    @Mock private lateinit var mockSecureSettings: SecureSettings
-    @Mock private lateinit var sysuiState: SysUiState
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags())
-        mDependency.injectTestDependency(SysUiState::class.java, sysuiState)
-        mDependency.injectMockDependency(DialogTransitionAnimator::class.java)
-        whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState)
-        whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java)))
-            .thenReturn(sysuiDialog)
-        whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext))
-
-        whenever(mockUserTracker.userId).thenReturn(context.userId)
-        if (Looper.myLooper() == null) Looper.prepare()
-
-        mContrastDialogDelegate =
-            ContrastDialogDelegate(
-                sysuiDialogFactory,
-                mainExecutor,
-                mockUiModeManager,
-                mockUserTracker,
-                mockSecureSettings
-            )
-
-        mContrastDialogDelegate.createDialog()
-        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
-        verify(sysuiDialog).setView(viewCaptor.capture())
-        whenever(sysuiDialog.requireViewById(anyInt()) as View?).then {
-            viewCaptor.value.requireViewById(it.getArgument(0))
-        }
-    }
-
-    @Test
-    fun testClickButtons_putsContrastInSettings() {
-        mContrastDialogDelegate.onCreate(sysuiDialog, null)
-
-        mContrastDialogDelegate.contrastButtons.forEach {
-            (contrastLevel: Int, clickedButton: FrameLayout) ->
-            clickedButton.performClick()
-            mainExecutor.runAllReady()
-            verify(mockSecureSettings)
-                .putFloatForUser(
-                    eq(Settings.Secure.CONTRAST_LEVEL),
-                    eq(fromContrastLevel(contrastLevel)),
-                    eq(context.userId)
-                )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index ef15d21..00f94a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -91,33 +91,40 @@
         }
     private val testScope = kosmos.testScope
 
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
     private var commandQueue = kosmos.fakeCommandQueue
     private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
 
-    private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
-    private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
-    private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
-    private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
-    private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
-    private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
-    private val fromAlternateBouncerTransitionInteractor =
+    private val fromLockscreenTransitionInteractor by lazy {
+        kosmos.fromLockscreenTransitionInteractor
+    }
+    private val fromDreamingTransitionInteractor by lazy { kosmos.fromDreamingTransitionInteractor }
+    private val fromDozingTransitionInteractor by lazy { kosmos.fromDozingTransitionInteractor }
+    private val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
+    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+    private val fromAodTransitionInteractor by lazy { kosmos.fromAodTransitionInteractor }
+    private val fromAlternateBouncerTransitionInteractor by lazy {
         kosmos.fromAlternateBouncerTransitionInteractor
-    private val fromPrimaryBouncerTransitionInteractor =
+    }
+    private val fromPrimaryBouncerTransitionInteractor by lazy {
         kosmos.fromPrimaryBouncerTransitionInteractor
-    private val fromDreamingLockscreenHostedTransitionInteractor =
+    }
+    private val fromDreamingLockscreenHostedTransitionInteractor by lazy {
         kosmos.fromDreamingLockscreenHostedTransitionInteractor
-    private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
+    }
+    private val fromGlanceableHubTransitionInteractor by lazy {
+        kosmos.fromGlanceableHubTransitionInteractor
+    }
 
-    private val powerInteractor = kosmos.powerInteractor
-    private val communalInteractor = kosmos.communalInteractor
-    private val dockManager = kosmos.fakeDockManager
+    private val powerInteractor by lazy { kosmos.powerInteractor }
+    private val communalInteractor by lazy { kosmos.communalInteractor }
+    private val dockManager by lazy { kosmos.fakeDockManager }
 
     companion object {
         @JvmStatic
@@ -633,7 +640,7 @@
             // GIVEN a prior transition has run to DREAMING
             keyguardRepository.setDreamingWithOverlay(true)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
-            runCurrent()
+            advanceTimeBy(60L)
 
             // WHEN the device wakes up without a keyguard
             keyguardRepository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 768d446..40663ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
 import com.android.systemui.keyguard.data.repository.keyguardClockRepository
@@ -56,7 +57,7 @@
 class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
-    val underTest = kosmos.keyguardClockViewModel
+    val underTest by lazy { kosmos.keyguardClockViewModel }
     val res = context.resources
 
     @Mock lateinit var clockController: ClockController
@@ -96,6 +97,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
         testScope.runTest {
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -110,6 +112,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
         testScope.runTest {
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -124,6 +127,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun currentClockLayout_singleShade_smallClock_smallClock() =
         testScope.runTest {
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -193,6 +197,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun testClockSize_dynamicClockSize() =
         testScope.runTest {
             with(kosmos) {
@@ -216,6 +221,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun isLargeClockVisible_whenSmallClockSize_isFalse() =
         testScope.runTest {
             val value by collectLastValue(underTest.isLargeClockVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 3b6a88a..5dbfe47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -25,6 +25,8 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -91,8 +93,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -105,8 +107,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -119,8 +121,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
@@ -133,8 +135,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 890e1e0..0998c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -94,6 +94,8 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
@@ -1576,17 +1578,19 @@
     }
 
     @Test
+    @DisableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreview_flagDisabled() {
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
         verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
     }
 
     @Test
+    @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+    @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
     public void testUpdateGeneratedPreview_userLocked() {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
 
         mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1594,9 +1598,9 @@
     }
 
     @Test
+    @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+    @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
     public void testUpdateGeneratedPreview_userUnlocked() {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
@@ -1605,9 +1609,9 @@
     }
 
     @Test
+    @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+    @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
     public void testUpdateGeneratedPreview_doesNotSetTwice() {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
@@ -1617,9 +1621,11 @@
     }
 
     @Test
+    @EnableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreviewWithDataParcel_userLocked() throws InterruptedException {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
 
         mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1628,10 +1634,12 @@
     }
 
     @Test
+    @EnableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreviewWithDataParcel_userUnlocked()
             throws InterruptedException {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
@@ -1641,10 +1649,12 @@
     }
 
     @Test
+    @EnableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreviewWithDataParcel_doesNotSetTwice()
             throws InterruptedException {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
index 629c663..bc947fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -3,6 +3,7 @@
 import android.content.ComponentName
 import android.service.quicksettings.Tile
 import android.testing.AndroidTestingRunner
+import android.widget.Switch
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.qs.QSTile
@@ -66,15 +67,19 @@
         assertThat(proto?.hasBooleanState()).isFalse()
     }
 
+    /**
+     * The [QSTile.AdapterState.expandedAccessibilityClassName] setting to [Switch] results in the
+     * proto having a booleanState. The value of that boolean is true iff the tile is active.
+     */
     @Test
-    fun booleanState_ACTIVE() {
+    fun adapterState_ACTIVE() {
         val state =
-            QSTile.BooleanState().apply {
+            QSTile.AdapterState().apply {
                 spec = TEST_SPEC
                 label = TEST_LABEL
                 secondaryLabel = TEST_SUBTITLE
                 state = Tile.STATE_ACTIVE
-                value = true
+                expandedAccessibilityClassName = Switch::class.java.name
             }
         val proto = state.toProto()
 
@@ -89,6 +94,33 @@
         assertThat(proto?.booleanState).isTrue()
     }
 
+    /**
+     * Similar to [adapterState_ACTIVE], the use of
+     * [QSTile.AdapterState.expandedAccessibilityClassName] signals that the tile is toggleable.
+     */
+    @Test
+    fun adapterState_INACTIVE() {
+        val state =
+            QSTile.AdapterState().apply {
+                spec = TEST_SPEC
+                label = TEST_LABEL
+                secondaryLabel = TEST_SUBTITLE
+                state = Tile.STATE_INACTIVE
+                expandedAccessibilityClassName = Switch::class.java.name
+            }
+        val proto = state.toProto()
+
+        assertThat(proto).isNotNull()
+        assertThat(proto?.hasSpec()).isTrue()
+        assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+        assertThat(proto?.hasComponentName()).isFalse()
+        assertThat(proto?.label).isEqualTo(TEST_LABEL)
+        assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+        assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+        assertThat(proto?.hasBooleanState()).isTrue()
+        assertThat(proto?.booleanState).isFalse()
+    }
+
     @Test
     fun noSpec_returnsNull() {
         val state =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index e2a3fac6..ad87315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
+import com.android.internal.telephony.flags.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,10 +33,12 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
+import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -44,11 +46,15 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class AirplaneModeTileTest : SysuiTestCase() {
+
     @Mock
     private lateinit var mHost: QSHost
     @Mock
@@ -62,7 +68,9 @@
     @Mock
     private lateinit var mBroadcastDispatcher: BroadcastDispatcher
     @Mock
-    private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
+    private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+    @Mock
+    private lateinit var mConnectivityManager: ConnectivityManager
     @Mock
     private lateinit var mGlobalSettings: GlobalSettings
     @Mock
@@ -72,13 +80,15 @@
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
+    @Mock
+    private lateinit var mClickJob: Job
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         Mockito.`when`(mHost.context).thenReturn(mContext)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
-
+        Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
         mTile = AirplaneModeTile(
             mHost,
             mUiEventLogger,
@@ -90,7 +100,7 @@
             mActivityStarter,
             mQsLogger,
             mBroadcastDispatcher,
-            mConnectivityManager,
+            mLazyConnectivityManager,
             mGlobalSettings,
             mUserTracker)
     }
@@ -120,4 +130,24 @@
         assertThat(state.icon)
             .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
     }
+
+    @Test
+    fun handleClick_noSatelliteFeature_directSetAirplaneMode() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+        mTile.handleClick(null)
+
+        verify(mConnectivityManager).setAirplaneMode(any())
+    }
+
+    @Test
+    fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        Mockito.`when`(mClickJob.isCompleted).thenReturn(false)
+        mTile.mClickJob = mClickJob
+
+        mTile.handleClick(null)
+
+        verify(mConnectivityManager, times(0)).setAirplaneMode(any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 830f08a..1ffbb7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,10 +9,11 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.telephony.flags.Flags
 import com.android.settingslib.Utils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.plugins.ActivityStarter
@@ -23,13 +24,14 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.BluetoothController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -37,6 +39,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -54,7 +57,7 @@
     @Mock private lateinit var uiEventLogger: QsEventLogger
     @Mock private lateinit var featureFlags: FeatureFlagsClassic
     @Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
-
+    @Mock private lateinit var clickJob: Job
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FakeBluetoothTile
 
@@ -191,6 +194,41 @@
     }
 
     @Test
+    fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(false)
+        `when`(clickJob.isCompleted).thenReturn(false)
+        tile.mClickJob = clickJob
+
+        tile.handleClick(null)
+
+        verify(bluetoothController, times(0)).setBluetoothEnabled(any())
+    }
+
+    @Test
+    fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(false)
+
+        tile.handleClick(null)
+
+        verify(bluetoothController).setBluetoothEnabled(any())
+    }
+
+    @Test
+    fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(true)
+
+        tile.handleClick(null)
+
+        verify(bluetoothTileDialogViewModel).showDialog(null)
+    }
+
+    @Test
     fun testMetadataListener_whenDisconnected_isUnregistered() {
         val state = QSTile.BooleanState()
         val cachedDevice = mock<CachedBluetoothDevice>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 81d0e06..2c2fcbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.shade
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -26,8 +28,8 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
@@ -164,10 +166,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
         val headerResourceHeight = 20
         val headerHelperHeight = 30
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
             .thenReturn(headerHelperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -187,10 +189,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
         val headerResourceHeight = 20
         val headerHelperHeight = 30
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
             .thenReturn(headerHelperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -400,8 +402,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSplitShadeLayout_isAlignedToGuideline() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         enableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
@@ -410,8 +412,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSinglePaneLayout_childrenHaveEqualMargins() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         disableSplitShade()
         underTest.updateResources()
         val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
@@ -427,8 +429,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         enableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
@@ -445,9 +447,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
@@ -468,9 +469,9 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
@@ -491,8 +492,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         setSmallScreen()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
@@ -512,8 +513,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSinglePaneShadeLayout_isAlignedToParent() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         disableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 4ae751b..f21def3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.shade
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -27,6 +29,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
@@ -67,6 +70,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 class NotificationsQSContainerControllerTest : SysuiTestCase() {
 
     private val view = mock<NotificationsQuickSettingsContainer>()
@@ -99,7 +103,6 @@
         MockitoAnnotations.initMocks(this)
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
-        mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         mContext.ensureTestableResources()
         whenever(view.context).thenReturn(mContext)
         whenever(view.resources).thenReturn(mContext.resources)
@@ -161,8 +164,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         val helperHeight = 30
         val resourceHeight = 20
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -182,8 +185,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         val helperHeight = 30
         val resourceHeight = 20
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -424,8 +427,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderHelperHeight = 200
         val largeScreenHeaderResourceHeight = 100
@@ -444,8 +447,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderHelperHeight = 200
         val largeScreenHeaderResourceHeight = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
index f0a457e..7d2b463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -31,16 +31,17 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class OngoingCallBackgroundContainerTest : SysuiTestCase() {
+class ChipBackgroundContainerTest : SysuiTestCase() {
 
-    private lateinit var underTest: OngoingCallBackgroundContainer
+    private lateinit var underTest: ChipBackgroundContainer
 
     @Before
     fun setUp() {
         allowTestableLooperAsMainThread()
         TestableLooper.get(this).runWithLooper {
-            val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null)
-            underTest = chipView.requireViewById(R.id.ongoing_call_chip_background)
+            val chipView =
+                LayoutInflater.from(context).inflate(R.layout.ongoing_activity_chip, null)
+            underTest = chipView.requireViewById(R.id.ongoing_activity_chip_background)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
index 7e25aa3..b8d4e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -38,17 +38,18 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class OngoingCallChronometerTest : SysuiTestCase() {
+class ChipChronometerTest : SysuiTestCase() {
 
-    private lateinit var textView: OngoingCallChronometer
+    private lateinit var textView: ChipChronometer
     private lateinit var doesNotFitText: String
 
     @Before
     fun setUp() {
         allowTestableLooperAsMainThread()
         TestableLooper.get(this).runWithLooper {
-            val chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
-            textView = chipView.findViewById(R.id.ongoing_call_chip_time)!!
+            val chipView =
+                LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+            textView = chipView.findViewById(R.id.ongoing_activity_chip_time)!!
             measureTextView()
             calculateDoesNotFixText()
         }
@@ -159,8 +160,8 @@
 
     private fun measureTextView() {
         textView.measure(
-                View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 745d20d..1eed420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.statusbar.notification.stack
 
+import android.platform.test.annotations.DisableFlags
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -69,8 +70,8 @@
     }
 
     @Test
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testShadeWidth_BasedOnFractionToShade() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         setFractionToShade(0f)
         setOnLockscreen(true)
 
@@ -85,8 +86,8 @@
     }
 
     @Test
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testShelfIsLong_WhenNotOnLockscreen() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         setFractionToShade(0f)
         setOnLockscreen(false)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b9312d3..4488799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -21,6 +21,7 @@
 import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
 import static android.provider.Settings.Global.HEADS_UP_ON;
 
+import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR;
 import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
 import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
 import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
@@ -70,6 +71,8 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.service.dreams.IDreamManager;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
@@ -105,8 +108,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.communal.data.repository.CommunalRepository;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
@@ -224,6 +225,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
+@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
 public class CentralSurfacesImplTest extends SysuiTestCase {
 
     private static final int FOLD_STATE_FOLDED = 0;
@@ -238,8 +240,6 @@
 
 
     private final TestScope mTestScope = mKosmos.getTestScope();
-    private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor();
-    private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository();
     @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -362,13 +362,9 @@
 
         // Set default value to avoid IllegalStateException.
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
         // Turn AOD on and toggle feature flag for jank fixes
         mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        if (!SceneContainerFlag.isEnabled()) {
-            mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
-        }
 
         IThermalService thermalService = mock(IThermalService.class);
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -528,7 +524,7 @@
                 mScreenLifecycle,
                 mWakefulnessLifecycle,
                 mPowerInteractor,
-                mCommunalInteractor,
+                mKosmos.getCommunalInteractor(),
                 mStatusBarStateController,
                 Optional.of(mBubbles),
                 () -> mNoteTaskController,
@@ -837,6 +833,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
     public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() {
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
         when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
@@ -848,7 +845,8 @@
     }
 
     @Test
-    public void testOccludingQSNotExpanded_transitionToAuthScrimmed() {
+    @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() {
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
 
         // GIVEN device occluded and panel is NOT expanded
@@ -862,6 +860,39 @@
     }
 
     @Test
+    @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() {
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // GIVEN device occluded and panel is NOT expanded
+        mCentralSurfaces.setBarStateForTest(SHADE);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
+
+        mCentralSurfaces.updateScrimController();
+
+        // Tests the safeguard to reset the scrimstate
+        verify(mScrimController, never()).transitionTo(any());
+    }
+
+    @Test
+    @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() {
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // GIVEN device occluded and panel is NOT expanded
+        mCentralSurfaces.setBarStateForTest(SHADE);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
+
+        mCentralSurfaces.updateScrimController();
+
+        // Tests the safeguard to reset the scrimstate
+        verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD));
+    }
+
+    @Test
+    @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
     public void testOccludingQSExpanded_transitionToAuthScrimmedShade() {
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
 
@@ -878,16 +909,18 @@
     @Test
     public void testEnteringGlanceableHub_updatesScrim() {
         // Transition to the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Communal)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState also transitions.
         verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
 
         // Transition away from the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Blank)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState goes back to UNLOCKED.
@@ -901,16 +934,18 @@
         when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
 
         // Transition to the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Communal)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState also transitions.
         verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
 
         // Transition away from the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Blank)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState goes back to UNLOCKED.
@@ -1105,18 +1140,16 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
     public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() {
-        mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
         mCentralSurfaces.updateResources();
 
         verify(mStatusBarWindowController, never()).refreshStatusBarHeight();
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
     public void updateResources_flagDisabled_updatesStatusBarWindowHeight() {
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
         mCentralSurfaces.updateResources();
 
         verify(mStatusBarWindowController).refreshStatusBarHeight();
@@ -1151,10 +1184,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1163,10 +1196,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1175,10 +1208,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1188,10 +1221,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1200,10 +1233,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1213,10 +1246,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1226,10 +1259,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 321;
@@ -1239,10 +1272,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 654;
@@ -1253,10 +1286,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 987;
@@ -1267,10 +1300,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 789;
@@ -1280,10 +1313,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 456;
@@ -1294,10 +1327,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 123;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index fd295b5..e834693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -26,6 +26,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -297,8 +299,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
         int keyguardSplitShadeTopMargin = 100;
         int largeScreenHeaderHeightResource = 70;
         when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
@@ -316,8 +318,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
         int keyguardSplitShadeTopMargin = 100;
         int largeScreenHeaderHeightHelper = 50;
         int largeScreenHeaderHeightResource = 70;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 66211c9..fdf77ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -426,7 +426,7 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
@@ -438,7 +438,7 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
@@ -452,7 +452,7 @@
                 StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
@@ -465,7 +465,7 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
@@ -477,21 +477,21 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
 
         // Ongoing call ended
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
 
         // Ongoing call started
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 05464f3..4d6798b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -106,7 +106,7 @@
     fun setUp() {
         allowTestableLooperAsMainThread()
         TestableLooper.get(this).runWithLooper {
-            chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+            chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
         }
 
         MockitoAnnotations.initMocks(this)
@@ -206,7 +206,7 @@
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
-        assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
                 .isEqualTo(0)
     }
 
@@ -222,7 +222,7 @@
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
-        assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
                 .isGreaterThan(0)
     }
 
@@ -237,7 +237,7 @@
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
-        assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
                 .isGreaterThan(0)
     }
 
@@ -472,7 +472,10 @@
 
         lateinit var newChipView: View
         TestableLooper.get(this).runWithLooper {
-            newChipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+            newChipView = LayoutInflater.from(mContext).inflate(
+                    R.layout.ongoing_activity_chip,
+                    null
+            )
         }
 
         // Change the chip view associated with the controller.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 3dee093..96c6eb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -887,6 +887,46 @@
     }
 
     @Test
+    fun removeGuestUser_shouldNotShowExitGuestDialog() {
+        createUserInteractor()
+        testScope.runTest {
+            val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+            userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+            userRepository.setSelectedUserInfo(guestUserInfo)
+
+            whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+            underTest.removeGuestUser(guestUserInfo.id, userInfo.id)
+            runCurrent()
+
+            verify(manager).markGuestForDeletion(guestUserInfo.id)
+            verify(activityManager).switchUser(userInfo.id)
+            assertThat(collectLastValue(underTest.dialogShowRequests)()).isNull()
+        }
+    }
+
+    @Test
+    fun resetGuestUser_shouldNotShowExitGuestDialog() {
+        createUserInteractor()
+        testScope.runTest {
+            val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+            val otherGuestUserInfo = createUserInfos(count = 1, includeGuest = true)[0]
+            userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+            userRepository.setSelectedUserInfo(guestUserInfo)
+
+            whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+            whenever(manager.createGuest(any())).thenReturn(otherGuestUserInfo)
+            underTest.removeGuestUser(guestUserInfo.id, UserHandle.USER_NULL)
+            runCurrent()
+
+            verify(manager).markGuestForDeletion(guestUserInfo.id)
+            verify(manager).createGuest(any())
+            verify(activityManager).switchUser(otherGuestUserInfo.id)
+            assertThat(collectLastValue(underTest.dialogShowRequests)())
+                .isEqualTo(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+        }
+    }
+
+    @Test
     fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() {
         createUserInteractor()
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aac3640..df78110 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -27,6 +27,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -2141,6 +2142,112 @@
         assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
     }
 
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+        // Drag first bubble, bubble should collapse
+        mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+        // Stop dragging, first bubble should be expanded
+        mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+        // Drag second bubble, bubble should collapse
+        mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+        // Stop dragging, first bubble should be expanded
+        mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        // Drag first bubble to dismiss
+        mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+        mBubbleController.dragBubbleToDismiss(mBubbleEntry.getKey());
+        // Second bubble is selected and expanded
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry2.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        // Drag second bubble to dismiss
+        mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+        mBubbleController.dragBubbleToDismiss(mBubbleEntry2.getKey());
+        // First bubble remains selected and expanded
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
     @Test
     public void doesNotRegisterSensitiveStateListener() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 9dcd946..8eef930 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,8 +15,6 @@
  */
 package com.android.systemui;
 
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -99,8 +97,22 @@
             .setProvideMainThread(true)
             .build();
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mSetFlagsClassRule =
+            new SetFlagsRule.ClassRule(
+                    android.app.Flags.class,
+                    android.hardware.biometrics.Flags.class,
+                    android.multiuser.Flags.class,
+                    android.net.platform.flags.Flags.class,
+                    android.os.Flags.class,
+                    android.service.controls.flags.Flags.class,
+                    com.android.internal.telephony.flags.Flags.class,
+                    com.android.server.notification.Flags.class,
+                    com.android.systemui.Flags.class);
+
+    // TODO(b/339471826): Fix Robolectric to execute the @ClassRule correctly
+    @Rule public final SetFlagsRule mSetFlagsRule =
+            isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule();
 
     @Rule(order = 10)
     public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
index d3ceb15..f5d02f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -38,4 +38,8 @@
                 )
             )
     }
+
+    fun setBaseUserRestriction() {
+        _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
new file mode 100644
index 0000000..8ad6087
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qrcodescanner
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index c4bf8ff..f50443e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -31,7 +31,11 @@
 
     private val mutableInputs = mutableListOf<Input>()
 
-    override fun handle(expandable: Expandable?, intent: Intent) {
+    override fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        handleDismissShadeShowOverLockScreenWhenLocked: Boolean
+    ) {
         mutableInputs.add(Input.Intent(expandable, intent))
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
new file mode 100644
index 0000000..ccfb609
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.actions
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
new file mode 100644
index 0000000..146c1ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.analytics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
new file mode 100644
index 0000000..9ad49f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
new file mode 100644
index 0000000..dcfcce7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr
+
+import android.content.res.mainResources
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule
+import com.android.systemui.qrcodescanner.qrCodeScannerController
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.qsQRCodeScannerTileConfig by
+    Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) }
+
+val Kosmos.qrCodeScannerTileDataInteractor by
+    Kosmos.Fixture {
+        QRCodeScannerTileDataInteractor(
+            backgroundCoroutineContext,
+            applicationCoroutineScope,
+            qrCodeScannerController
+        )
+    }
+
+val Kosmos.qrCodeScannerTileUserActionInteractor by
+    Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) }
+
+val Kosmos.qrCodeScannerTileMapper by
+    Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) }
+
+val Kosmos.qsQRCodeScannerViewModel by
+    Kosmos.Fixture {
+        QSTileViewModelImpl(
+            qsQRCodeScannerTileConfig,
+            { qrCodeScannerTileUserActionInteractor },
+            { qrCodeScannerTileDataInteractor },
+            { qrCodeScannerTileMapper },
+            fakeDisabledByPolicyInteractor,
+            fakeUserRepository,
+            fakeFalsingManager,
+            qsTileAnalytics,
+            qsTileLogger,
+            systemClock,
+            testDispatcher,
+            testScope.backgroundScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 617fc52..6b27079 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.data.repository
 
 import android.media.AudioDeviceInfo
+import android.media.AudioManager
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
@@ -29,10 +30,10 @@
 
 class FakeAudioRepository : AudioRepository {
 
-    private val mutableMode = MutableStateFlow(0)
+    private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
     override val mode: StateFlow<Int> = mutableMode.asStateFlow()
 
-    private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+    private val mutableRingerMode = MutableStateFlow(RingerMode(AudioManager.RINGER_MODE_NORMAL))
     override val ringerMode: StateFlow<RingerMode> = mutableRingerMode.asStateFlow()
 
     private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
@@ -53,7 +54,7 @@
                     audioStream = audioStream,
                     volume = 0,
                     minVolume = 0,
-                    maxVolume = 0,
+                    maxVolume = 10,
                     isAffectedByRingerMode = false,
                     isMuted = false,
                 )
@@ -67,8 +68,14 @@
         getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
     }
 
-    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
-        getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+        val modelState = getAudioStreamModelState(audioStream)
+        return if (modelState.value.isMuted == isMuted) {
+            false
+        } else {
+            modelState.update { it.copy(isMuted = isMuted) }
+            true
+        }
     }
 
     override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int =
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 2b18b7d..3f30b0a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -152,6 +152,16 @@
 }
 
 flag {
+    name: "remove_on_window_infos_changed_handler"
+    namespace: "accessibility"
+    description: "Updates onWindowInfosChanged() to run without posting to a handler."
+    bug: "333834990"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "reset_hover_event_timer_on_action_up"
     namespace: "accessibility"
     description: "Reset the timer for sending hover events on receiving ACTION_UP to guarantee the correct amount of time is available between taps."
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dc1155a..0fdf6d0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -260,7 +260,6 @@
         "connectivity_flags_lib",
         "dreams_flags_lib",
         "aconfig_new_storage_flags_lib",
-        "aconfigd_java_proto_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4f84149..58732fd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -159,6 +159,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * All information we are collecting about things that can happen that impact
@@ -409,26 +411,14 @@
                 com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
         final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
-        final long powerStatsThrottlePeriodCpu = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu);
-        final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio);
-        final long powerStatsThrottlePeriodWifi = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodWifi);
-        mBatteryStatsConfig =
+        BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder =
                 new BatteryStatsImpl.BatteryStatsConfig.Builder()
                         .setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
-                        .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
-                        .setPowerStatsThrottlePeriodMillis(
-                                BatteryConsumer.POWER_COMPONENT_CPU,
-                                powerStatsThrottlePeriodCpu)
-                        .setPowerStatsThrottlePeriodMillis(
-                                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                                powerStatsThrottlePeriodMobileRadio)
-                        .setPowerStatsThrottlePeriodMillis(
-                                BatteryConsumer.POWER_COMPONENT_WIFI,
-                                powerStatsThrottlePeriodWifi)
-                        .build();
+                        .setResetOnUnplugAfterSignificantCharge(
+                                resetOnUnplugAfterSignificantCharge);
+        setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString(
+                com.android.internal.R.string.config_powerStatsThrottlePeriods));
+        mBatteryStatsConfig = batteryStatsConfigBuilder.build();
         mPowerStatsUidResolver = new PowerStatsUidResolver();
         mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                 systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
@@ -515,6 +505,26 @@
         return config;
     }
 
+    private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder,
+            String configString) {
+        Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString);
+        while (matcher.find()) {
+            String powerComponentName = matcher.group(1);
+            long throttlePeriod;
+            try {
+                throttlePeriod = Long.parseLong(matcher.group(2));
+            } catch (NumberFormatException nfe) {
+                throw new IllegalArgumentException(
+                        "Invalid config_powerStatsThrottlePeriods format: " + configString);
+            }
+            if (powerComponentName.equals("*")) {
+                builder.setDefaultPowerStatsThrottlePeriodMillis(throttlePeriod);
+            } else {
+                builder.setPowerStatsThrottlePeriodMillis(powerComponentName, throttlePeriod);
+            }
+        }
+    }
+
     /**
      * Creates an instance of BatteryStatsService and restores data from stored state.
      */
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 827db57..5793758 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -29,6 +29,8 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -262,11 +264,11 @@
             Uri settingUri = Settings.Global.getUriFor(globalSetting);
             String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
             if (settingUri == null) {
-                log("setting uri is null for globalSetting " + globalSetting);
+                logErr("setting uri is null for globalSetting " + globalSetting);
                 continue;
             }
             if (propName == null) {
-                log("invalid prop name for globalSetting " + globalSetting);
+                logErr("invalid prop name for globalSetting " + globalSetting);
                 continue;
             }
 
@@ -294,7 +296,7 @@
                         for (String key : properties.getKeyset()) {
                             String propertyName = makePropertyName(scope, key);
                             if (propertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -306,7 +308,7 @@
                             // sys prop slot can be removed.
                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
                             if (aconfigPropertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -324,7 +326,7 @@
                         for (String key : properties.getKeyset()) {
                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
                             if (aconfigPropertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -354,7 +356,7 @@
 
                   if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
                       || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
-                    log("unable to construct system property for " + actualNamespace
+                    logErr("unable to construct system property for " + actualNamespace
                         + "/" + flagName);
                     continue;
                   }
@@ -383,18 +385,18 @@
 
     /**
      * apply flag local override in aconfig new storage
-     * @param props
-     * @return aconfigd socket return
+     * @param requests: request proto output stream
+     * @return aconfigd socket return as proto input stream
      */
-    public static StorageReturnMessages sendAconfigdRequests(StorageRequestMessages requests) {
+    static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
         // connect to aconfigd socket
         LocalSocket client = new LocalSocket();
         try{
             client.connect(new LocalSocketAddress(
                 "aconfigd", LocalSocketAddress.Namespace.RESERVED));
-            log("connected to aconfigd socket");
+            Slog.d(TAG, "connected to aconfigd socket");
         } catch (IOException ioe) {
-            log("failed to connect to aconfigd socket", ioe);
+            logErr("failed to connect to aconfigd socket", ioe);
             return null;
         }
 
@@ -404,43 +406,93 @@
             inputStream = new DataInputStream(client.getInputStream());
             outputStream = new DataOutputStream(client.getOutputStream());
         } catch (IOException ioe) {
-            log("failed to get local socket iostreams", ioe);
+            logErr("failed to get local socket iostreams", ioe);
             return null;
         }
 
         // send requests
         try {
-            byte[] requests_bytes = requests.toByteArray();
+            byte[] requests_bytes = requests.getBytes();
             outputStream.writeInt(requests_bytes.length);
             outputStream.write(requests_bytes, 0, requests_bytes.length);
-            log(requests.getMsgsCount() + " flag override requests sent to aconfigd");
+            Slog.d(TAG, "flag override requests sent to aconfigd");
         } catch (IOException ioe) {
-            log("failed to send requests to aconfigd", ioe);
+            logErr("failed to send requests to aconfigd", ioe);
             return null;
         }
 
         // read return
-        StorageReturnMessages return_msgs = null;
         try {
             int num_bytes = inputStream.readInt();
-            byte[] buffer = new byte[num_bytes];
-            inputStream.read(buffer, 0, num_bytes);
-            return_msgs = StorageReturnMessages.parseFrom(buffer);
-            log(return_msgs.getMsgsCount() + " acknowledgement received from aconfigd");
+            ProtoInputStream returns = new ProtoInputStream(inputStream);
+            Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
+            return returns;
         } catch (IOException ioe) {
-            log("failed to read requests return from aconfigd", ioe);
+            logErr("failed to read requests return from aconfigd", ioe);
             return null;
         }
+    }
 
-        return return_msgs;
+    /**
+     * serialize a flag override request
+     * @param proto
+     */
+    static void writeFlagOverrideRequest(
+        ProtoOutputStream proto, String packageName, String flagName, String flagValue,
+        boolean isLocal) {
+      long msgsToken = proto.start(StorageRequestMessages.MSGS);
+      long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal);
+      proto.end(msgToken);
+      proto.end(msgsToken);
+    }
+
+    /**
+     * deserialize a flag input proto stream and log
+     * @param proto
+     */
+    static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException {
+        while (true) {
+          switch (proto.nextField()) {
+            case (int) StorageReturnMessages.MSGS:
+              long msgsToken = proto.start(StorageReturnMessages.MSGS);
+              switch (proto.nextField()) {
+                case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE:
+                  Slog.d(TAG, "successfully handled override requests");
+                  long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
+                  proto.end(msgToken);
+                  break;
+                case (int) StorageReturnMessage.ERROR_MESSAGE:
+                  String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
+                  Slog.d(TAG, "override request failed: " + errmsg);
+                  break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                  break;
+                default:
+                  logErr("invalid message type, expecting only flag override return or error message");
+                  break;
+              }
+              proto.end(msgsToken);
+              break;
+            case ProtoInputStream.NO_MORE_FIELDS:
+              return;
+            default:
+              logErr("invalid message type, expect storage return message");
+              break;
+          }
+        }
     }
 
     /**
      * apply flag local override in aconfig new storage
      * @param props
      */
-    public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
-        StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+    static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+        int num_requests = 0;
+        ProtoOutputStream requests = new ProtoOutputStream();
         for (String flagName : props.getKeyset()) {
             String flagValue = props.getString(flagName, null);
             if (flagName == null || flagValue == null) {
@@ -449,32 +501,35 @@
 
             int idx = flagName.indexOf(":");
             if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
-                log("invalid local flag override: " + flagName);
+                logErr("invalid local flag override: " + flagName);
                 continue;
             }
             String actualNamespace = flagName.substring(0, idx);
             String fullFlagName = flagName.substring(idx+1);
             idx = fullFlagName.lastIndexOf(".");
             if (idx == -1) {
-              log("invalid flag name: " + fullFlagName);
+              logErr("invalid flag name: " + fullFlagName);
               continue;
             }
             String packageName = fullFlagName.substring(0, idx);
             String realFlagName = fullFlagName.substring(idx+1);
-
-            StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
-                StorageRequestMessage.FlagOverrideMessage.newBuilder();
-            override_msg_builder.setPackageName(packageName);
-            override_msg_builder.setFlagName(realFlagName);
-            override_msg_builder.setFlagValue(flagValue);
-            override_msg_builder.setIsLocal(true);
-
-            StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
-            request_builder.setFlagOverrideMessage(override_msg_builder.build());
-            requests_builder.addMsgs(request_builder.build());
+            writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+            ++num_requests;
         }
-        StorageRequestMessages requests = requests_builder.build();
-        StorageReturnMessages acks = sendAconfigdRequests(requests);
+
+        if (num_requests == 0) {
+          return;
+        }
+
+        // send requests to aconfigd and obtain the return byte buffer
+        ProtoInputStream returns = sendAconfigdRequests(requests);
+
+        // deserialize back using proto input stream
+        try {
+          parseAndLogAconfigdReturn(returns);
+        } catch (IOException ioe) {
+            logErr("failed to parse aconfigd return", ioe);
+        }
     }
 
     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -517,7 +572,7 @@
         for (String property_name : property_names) {
             String[] segments = property_name.split("\\.");
             if (segments.length < 3) {
-                log("failed to extract category name from property " + property_name);
+                logErr("failed to extract category name from property " + property_name);
                 continue;
             }
             categories.add(segments[2]);
@@ -545,14 +600,16 @@
         return propertyName;
     }
 
+
     /**
      * stage flags in aconfig new storage
      * @param propsToStage
      */
     @VisibleForTesting
     static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
-        // create storage request proto
-        StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+        // write aconfigd requests proto to proto output stream
+        int num_requests = 0;
+        ProtoOutputStream requests = new ProtoOutputStream();
         for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
             String actualNamespace = entry.getKey();
             HashMap<String, String> flagValuesToStage = entry.getValue();
@@ -560,26 +617,29 @@
                 String stagedValue = flagValuesToStage.get(fullFlagName);
                 int idx = fullFlagName.lastIndexOf(".");
                 if (idx == -1) {
-                    log("invalid flag name: " + fullFlagName);
+                    logErr("invalid flag name: " + fullFlagName);
                     continue;
                 }
                 String packageName = fullFlagName.substring(0, idx);
                 String flagName = fullFlagName.substring(idx+1);
-
-                StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
-                    StorageRequestMessage.FlagOverrideMessage.newBuilder();
-                override_msg_builder.setPackageName(packageName);
-                override_msg_builder.setFlagName(flagName);
-                override_msg_builder.setFlagValue(stagedValue);
-                override_msg_builder.setIsLocal(false);
-
-                StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
-                request_builder.setFlagOverrideMessage(override_msg_builder.build());
-                requests_builder.addMsgs(request_builder.build());
+                writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false);
+                ++num_requests;
             }
         }
-        StorageRequestMessages requests = requests_builder.build();
-        StorageReturnMessages acks = sendAconfigdRequests(requests);
+
+        if (num_requests == 0) {
+          return;
+        }
+
+        // send requests to aconfigd and obtain the return
+        ProtoInputStream returns = sendAconfigdRequests(requests);
+
+        // deserialize back using proto input stream
+        try {
+          parseAndLogAconfigdReturn(returns);
+        } catch (IOException ioe) {
+            logErr("failed to parse aconfigd return", ioe);
+        }
     }
 
     /**
@@ -620,7 +680,7 @@
       for (String flagName : properties.getKeyset()) {
         int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
         if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
-          log("invalid staged flag: " + flagName);
+          logErr("invalid staged flag: " + flagName);
           continue;
         }
         String actualNamespace = flagName.substring(0, idx);
@@ -671,7 +731,7 @@
             }
             value = "";
         } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
-            log("key=" + key + " value=" + value + " exceeds system property max length.");
+            logErr("key=" + key + " value=" + value + " exceeds system property max length.");
             return;
         }
 
@@ -681,11 +741,11 @@
             // Failure to set a property can be caused by SELinux denial. This usually indicates
             // that the property wasn't allowlisted in sepolicy.
             // No need to report it on all user devices, only on debug builds.
-            log("Unable to set property " + key + " value '" + value + "'", e);
+            logErr("Unable to set property " + key + " value '" + value + "'", e);
         }
     }
 
-    private static void log(String msg, Exception e) {
+    private static void logErr(String msg, Exception e) {
         if (Build.IS_DEBUGGABLE) {
             Slog.wtf(TAG, msg, e);
         } else {
@@ -693,7 +753,7 @@
         }
     }
 
-    private static void log(String msg) {
+    private static void logErr(String msg) {
         if (Build.IS_DEBUGGABLE) {
             Slog.wtf(TAG, msg);
         } else {
@@ -711,7 +771,7 @@
 
             br.close();
         } catch (IOException ioe) {
-            log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+            logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
         }
         return content;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 46061a5..275c930 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -206,6 +206,10 @@
         launchDeviceDiscovery();
         startQueuedActions();
         if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+            if (hasAction(RequestActiveSourceAction.class)) {
+                Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
+                removeAction(RequestActiveSourceAction.class);
+            }
             addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
                 @Override
                 public void onComplete(int result) {
@@ -1308,6 +1312,8 @@
                 mService.sendCecCommand(
                         HdmiCecMessageBuilder.buildActiveSource(
                                 getDeviceInfo().getLogicalAddress(), activePath));
+                updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath,
+                        "HdmiCecLocalDeviceTv#launchRoutingControl()");
             }
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 936e8b6..05c4aa6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1646,6 +1646,13 @@
             case Constants.MESSAGE_ROUTING_CHANGE:
             case Constants.MESSAGE_SET_STREAM_PATH:
             case Constants.MESSAGE_TEXT_VIEW_ON:
+                // RequestActiveSourceAction is started after the TV finished logical address
+                // allocation. This action is used by the TV to get the active source from the CEC
+                // network. If the TV sent a source changing CEC message, this action does not have
+                // to continue anymore.
+                if (isTvDevice()) {
+                    tv().removeAction(RequestActiveSourceAction.class);
+                }
                 sendCecCommandWithRetries(command, callback);
                 break;
             default:
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index d250416..539a00d 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -21,13 +21,20 @@
 import android.util.Slog;
 
 /**
- * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
+ * panels.
+ * This action has a delay before sending <Request Active Source>. This is because it should wait
+ * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
+ * received or the TV switched to another input.
  */
 public class RequestActiveSourceAction extends HdmiCecFeatureAction {
     private static final String TAG = "RequestActiveSourceAction";
 
+    // State to wait for the LauncherX to call the CEC API.
+    private static final int STATE_WAIT_FOR_LAUNCHERX_API_CALL = 1;
+
     // State to wait for the <Active Source> message.
-    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1;
+    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 2;
 
     // Number of retries <Request Active Source> is sent if no device answers this message.
     private static final int MAX_SEND_RETRY_COUNT = 1;
@@ -43,10 +50,12 @@
     boolean start() {
         Slog.v(TAG, "RequestActiveSourceAction started.");
 
-        sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+        mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
 
-        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
-        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+        // We wait for default timeout to allow the message triggered by the LauncherX API call to
+        // be sent by the TV and another default timeout in case the message has to be answered
+        // (e.g. TV sent a <Set Stream Path> or <Routing Change>).
+        addTimer(mState, HdmiConfig.TIMEOUT_MS * 2);
         return true;
     }
 
@@ -65,13 +74,23 @@
         if (mState != state) {
             return;
         }
-        if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) {
-            if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+
+        switch (mState) {
+            case STATE_WAIT_FOR_LAUNCHERX_API_CALL:
+                mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
                 sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
                 addTimer(mState, HdmiConfig.TIMEOUT_MS);
-            } else {
-                finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
-            }
+                return;
+            case STATE_WAIT_FOR_ACTIVE_SOURCE:
+                if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+                    sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+                    addTimer(mState, HdmiConfig.TIMEOUT_MS);
+                } else {
+                    finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+                }
+                return;
+            default:
+                return;
         }
     }
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8685d2c..8e85b81 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2671,24 +2671,6 @@
         return null;
     }
 
-    private static class PointerDisplayIdChangedArgs {
-        final int mPointerDisplayId;
-        final float mXPosition;
-        final float mYPosition;
-        PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
-            mPointerDisplayId = pointerDisplayId;
-            mXPosition = xPosition;
-            mYPosition = yPosition;
-        }
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
-    @VisibleForTesting
-    void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
-        // TODO(b/311416205): Remove.
-    }
-
     @Override
     @EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
     public void registerStickyModifierStateListener(
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 1c14fc1..fff0e6e 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -133,6 +133,13 @@
         }
     }
 
+    @Override
+    public void onDispatched(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
+    }
+
     /**
      * Updates the IME request tracking token with new information available in IMMS.
      *
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b709174..e862c7e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -443,7 +443,16 @@
             mCurId = info.getId();
             mLastBindTime = SystemClock.uptimeMillis();
 
-            addFreshWindowToken();
+            final int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
+            mCurToken = new Binder();
+            mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
+            if (DEBUG) {
+                Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+                        + displayIdToShowIme);
+            }
+            mWindowManagerInternal.addWindowToken(mCurToken,
+                    WindowManager.LayoutParams.TYPE_INPUT_METHOD,
+                    displayIdToShowIme, null /* options */);
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                     null, null, null, mCurId, mCurSeq, false);
@@ -471,22 +480,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void addFreshWindowToken() {
-        int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
-        mCurToken = new Binder();
-
-        mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
-
-        if (DEBUG) {
-            Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
-                    + displayIdToShowIme);
-        }
-        mWindowManagerInternal.addWindowToken(mCurToken,
-                WindowManager.LayoutParams.TYPE_INPUT_METHOD,
-                displayIdToShowIme, null /* options */);
-    }
-
-    @GuardedBy("ImfLock.class")
     private void unbindMainConnection() {
         mContext.unbindService(mMainConnection);
         mHasMainConnection = false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 6339686..458c359 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Parcel;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
@@ -323,4 +324,24 @@
         return SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry,
                 requiredSubtypeMode);
     }
+
+    /**
+     * Marshals the given {@link InputMethodInfo} into a byte array.
+     *
+     * @param imi {@link InputMethodInfo} to be marshalled
+     * @return a byte array where the given {@link InputMethodInfo} is marshalled
+     */
+    @NonNull
+    static byte[] marshal(@NonNull InputMethodInfo imi) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.writeTypedObject(imi, 0);
+            return parcel.marshall();
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 954f9bc..46c5772 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -262,6 +262,7 @@
     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
+    private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
 
     /**
      * When set, {@link #startInputUncheckedLocked} will return
@@ -285,6 +286,9 @@
     final Resources mRes;
     private final Handler mHandler;
 
+    @NonNull
+    private final Handler mPackageMonitorHandler;
+
     @MultiUserUnawareField
     @UserIdInt
     @GuardedBy("ImfLock.class")
@@ -1288,13 +1292,14 @@
     }
 
     public InputMethodManagerService(Context context) {
-        this(context, null, null);
+        this(context, null, null, null);
     }
 
     @VisibleForTesting
     InputMethodManagerService(
             Context context,
             @Nullable ServiceThread serviceThreadForTesting,
+            @Nullable ServiceThread packageMonitorThreadForTesting,
             @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
         synchronized (ImfLock.class) {
             mContext = context;
@@ -1312,6 +1317,17 @@
                                     true /* allowIo */);
             thread.start();
             mHandler = Handler.createAsync(thread.getLooper(), this);
+            {
+                final ServiceThread packageMonitorThread =
+                        packageMonitorThreadForTesting != null
+                                ? packageMonitorThreadForTesting
+                                : new ServiceThread(
+                                        PACKAGE_MONITOR_THREAD_NAME,
+                                        Process.THREAD_PRIORITY_FOREGROUND,
+                                        true /* allowIo */);
+                packageMonitorThread.start();
+                mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper());
+            }
             SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
             mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
                     ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
@@ -1487,7 +1503,8 @@
         // Note that in b/197848765 we want to see if we can keep the binding alive for better
         // profile switching.
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.unbindCurrentMethod();
+        final var bindingController = userData.mBindingController;
+        bindingController.unbindCurrentMethod();
 
         unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
 
@@ -1585,7 +1602,7 @@
                     }
                 }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
 
-                mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
+                mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler);
                 mSettingsObserver.registerContentObserverLocked(currentUserId);
 
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -1706,9 +1723,10 @@
             // Check if selected IME of current user supports handwriting.
             if (userId == mCurrentUserId) {
                 final var userData = mUserDataRepository.getOrCreate(userId);
-                return userData.mBindingController.supportsStylusHandwriting()
+                final var bindingController = userData.mBindingController;
+                return bindingController.supportsStylusHandwriting()
                         && (!connectionless
-                        || userData.mBindingController.supportsConnectionlessStylusHandwriting());
+                        || bindingController.supportsConnectionlessStylusHandwriting());
             }
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
@@ -1904,7 +1922,8 @@
             //  following dependencies also need to be user independent: mCurClient, mBoundToMethod,
             //  getCurMethodLocked(), and mMenuController.
             final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            mCurClient.mClient.onUnbindMethod(userData.mBindingController.getSequenceNumber(),
+            final var bindingController = userData.mBindingController;
+            mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
                     unbindClientReason);
             mCurClient.mSessionRequested = false;
             mCurClient.mSessionRequestedForAccessibility = false;
@@ -1984,13 +2003,14 @@
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        final var bindingController = userData.mBindingController;
         final StartInputInfo info = new StartInputInfo(mCurrentUserId,
                 getCurTokenLocked(),
-                mCurTokenDisplayId, userData.mBindingController.getCurId(), startInputReason,
+                getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
                 restarting, UserHandle.getUserId(mCurClient.mUid),
                 mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
                 mImeBindingState.mFocusedWindowSoftInputMode,
-                userData.mBindingController.getSequenceNumber());
+                bindingController.getSequenceNumber());
         mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
         mStartInputHistory.addEntry(info);
 
@@ -2002,7 +2022,7 @@
         if (mCurrentUserId == UserHandle.getUserId(
                 mCurClient.mUid)) {
             mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */,
-                    UserHandle.getAppId(userData.mBindingController.getCurMethodUid()),
+                    UserHandle.getAppId(bindingController.getCurMethodUid()),
                     mCurClient.mUid, true /* direct */);
         }
 
@@ -2023,20 +2043,20 @@
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
-        final var curId = userData.mBindingController.getCurId();
+        final var curId = bindingController.getCurId();
         final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
                 .getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
                 createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
-        if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+        if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
             mHwController.setInkWindowInitializer(new InkWindowInitializer());
         }
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.mSession, accessibilityInputMethodSessions,
                 (session.mChannel != null ? session.mChannel.dup() : null),
-                curId, userData.mBindingController.getSequenceNumber(), suppressesSpellChecker);
+                curId, bindingController.getSequenceNumber(), suppressesSpellChecker);
     }
 
     @GuardedBy("ImfLock.class")
@@ -2130,7 +2150,8 @@
         final boolean connectionWasActive = mCurInputConnection != null;
 
         // Bump up the sequence for this client and attach it.
-        userData.mBindingController.advanceSequenceNumber();
+        final var bindingController = userData.mBindingController;
+        bindingController.advanceSequenceNumber();
 
         mCurClient = cs;
         mCurInputConnection = inputConnection;
@@ -2153,7 +2174,6 @@
         if (connectionIsActive != connectionWasActive) {
             mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
         }
-        final var bindingController = userData.mBindingController;
 
         // If configured, we want to avoid starting up the IME if it is not supposed to be showing
         if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -2171,7 +2191,7 @@
         // display ID.
         final String curId = bindingController.getCurId();
         if (curId != null && curId.equals(bindingController.getSelectedMethodId())
-                && mDisplayIdToShowIme == mCurTokenDisplayId) {
+                && mDisplayIdToShowIme == getCurTokenDisplayIdLocked()) {
             if (cs.mCurSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
@@ -2304,7 +2324,8 @@
     @Nullable
     private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
             @NonNull ClientState cs) {
-        if (userData.mBindingController.hasMainConnection()) {
+        final var bindingController = userData.mBindingController;
+        if (bindingController.hasMainConnection()) {
             if (getCurMethodLocked() != null) {
                 // Return to client, and we will get back with it when
                 // we have had a session made for it.
@@ -2313,10 +2334,10 @@
                 return new InputBindResult(
                         InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
                         null, null, null,
-                        userData.mBindingController.getCurId(),
-                        userData.mBindingController.getSequenceNumber(), false);
+                        bindingController.getCurId(),
+                        bindingController.getSequenceNumber(), false);
             } else {
-                final long lastBindTime = userData.mBindingController.getLastBindTime();
+                final long lastBindTime = bindingController.getLastBindTime();
                 long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
                 if (bindingDuration < TIME_TO_RECONNECT) {
                     // In this case we have connected to the service, but
@@ -2329,8 +2350,8 @@
                     return new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                             null, null, null,
-                            userData.mBindingController.getCurId(),
-                            userData.mBindingController.getSequenceNumber(), false);
+                            bindingController.getCurId(),
+                            bindingController.getSequenceNumber(), false);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
                             getSelectedMethodIdLocked(), bindingDuration, 0);
@@ -2377,7 +2398,7 @@
     void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
-                    + mCurTokenDisplayId);
+                    + getCurTokenDisplayIdLocked());
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
                 getInputMethodNavButtonFlagsLocked());
@@ -2457,13 +2478,14 @@
 
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.setSelectedMethodId(null);
+        final var bindingController =
+                mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController;
+        bindingController.setSelectedMethodId(null);
 
         // Callback before clean-up binding states.
         // TODO(b/338461930): Check if this is still necessary or not.
         onUnbindCurrentMethodByReset();
-        userData.mBindingController.unbindCurrentMethod();
+        bindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
 
@@ -2666,9 +2688,10 @@
         }
         // Whether the current display has a navigation bar. When this is false (e.g. emulator),
         // the IME should not draw the IME navigation bar.
+        final int tokenDisplayId = getCurTokenDisplayIdLocked();
         final boolean hasNavigationBar = mWindowManagerInternal
-                .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
-                        ? mCurTokenDisplayId : DEFAULT_DISPLAY);
+                .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
+                        ? tokenDisplayId : DEFAULT_DISPLAY);
         final boolean canImeDrawsImeNavBar =
                 mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
         final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
@@ -2764,8 +2787,8 @@
             // Note that we still need to update IME status when focusing external display
             // that does not support system decoration and fallback to show IME on default
             // display since it is intentional behavior.
-            if (mCurTokenDisplayId != topFocusedDisplayId
-                    && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) {
+            final int tokenDisplayId = getCurTokenDisplayIdLocked();
+            if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
                 return;
             }
             mImeWindowVis = vis;
@@ -2828,7 +2851,7 @@
             Slog.d(TAG, "IME window vis: " + vis
                     + " active: " + (vis & InputMethodService.IME_ACTIVE)
                     + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
-                    + " displayId: " + mCurTokenDisplayId);
+                    + " displayId: " + getCurTokenDisplayIdLocked());
         }
         final IBinder focusedWindowToken = mImeBindingState != null
                 ? mImeBindingState.mFocusedWindow : null;
@@ -2857,7 +2880,7 @@
             }
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBarManagerInternal != null) {
-                mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
+                mStatusBarManagerInternal.setImeWindowStatus(getCurTokenDisplayIdLocked(),
                         getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher);
             }
         } finally {
@@ -3546,13 +3569,14 @@
             final InputBindResult result;
             synchronized (ImfLock.class) {
                 final var userData = mUserDataRepository.getOrCreate(userId);
+                final var bindingController = userData.mBindingController;
                 // If the system is not yet ready, we shouldn't be running third party code.
                 if (!mSystemReady) {
                     return new InputBindResult(
                             InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
                             null /* method */, null /* accessibilitySessions */, null /* channel */,
                             getSelectedMethodIdLocked(),
-                            userData.mBindingController.getSequenceNumber(),
+                            bindingController.getSequenceNumber(),
                             false /* isInputMethodSuppressingSpellChecker */);
                 }
                 final ClientState cs = mClientController.getClient(client.asBinder());
@@ -3761,7 +3785,7 @@
                 // window token removed.
                 // Note that we can trust client's display ID as long as it matches
                 // to the display ID obtained from the window.
-                if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
+                if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
                     userData.mBindingController.unbindCurrentMethod();
                 }
             }
@@ -4143,7 +4167,7 @@
                 }
                 // This should probably use the caller's display id, but because this is unsupported
                 // and maintained only for compatibility, there's no point in fixing it.
-                curTokenDisplayId = mCurTokenDisplayId;
+                curTokenDisplayId = getCurTokenDisplayIdLocked();
             }
             return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
         });
@@ -4224,8 +4248,9 @@
         // a new Stylus is detected. If IME supports handwriting, and we don't have
         // handwriting initialized, lets do it now.
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        final var bindingController = userData.mBindingController;
         if (!mHwController.getCurrentRequestId().isPresent()
-                && userData.mBindingController.supportsStylusHandwriting()) {
+                && bindingController.supportsStylusHandwriting()) {
             scheduleResetStylusHandwriting();
         }
     }
@@ -4407,9 +4432,10 @@
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
             final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
             final long token = proto.start(fieldId);
             proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
-            proto.write(CUR_SEQ, userData.mBindingController.getSequenceNumber());
+            proto.write(CUR_SEQ, bindingController.getSequenceNumber());
             proto.write(CUR_CLIENT, Objects.toString(mCurClient));
             mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
             proto.write(LAST_IME_TARGET_WINDOW_NAME,
@@ -4419,13 +4445,13 @@
             if (mCurEditorInfo != null) {
                 mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
             }
-            proto.write(CUR_ID, userData.mBindingController.getCurId());
+            proto.write(CUR_ID, bindingController.getCurId());
             mVisibilityStateComputer.dumpDebug(proto, fieldId);
             proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
             proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
-            proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
+            proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked());
             proto.write(SYSTEM_READY, mSystemReady);
-            proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection());
+            proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -4537,7 +4563,8 @@
         final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
-                        show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
+                        show, mImeBindingState.mFocusedWindow, requestToken,
+                        getCurTokenDisplayIdLocked());
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
                 mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
                 info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
@@ -4796,10 +4823,11 @@
             case MSG_RESET_HANDWRITING: {
                 synchronized (ImfLock.class) {
                     final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                    if (userData.mBindingController.supportsStylusHandwriting()
+                    final var bindingController = userData.mBindingController;
+                    if (bindingController.supportsStylusHandwriting()
                             && getCurMethodLocked() != null && hasSupportedStylusLocked()) {
                         Slog.d(TAG, "Initializing Handwriting Spy");
-                        mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
+                        mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked());
                     } else {
                         mHwController.reset();
                     }
@@ -4822,11 +4850,12 @@
                         return true;
                     }
                     final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+                    final var bindingController = userData.mBindingController;
                     final HandwritingModeController.HandwritingSession session =
                             mHwController.startHandwritingSession(
                                     msg.arg1 /*requestId*/,
                                     msg.arg2 /*pid*/,
-                                    userData.mBindingController.getCurMethodUid(),
+                                    bindingController.getCurMethodUid(),
                                     mImeBindingState.mFocusedWindow);
                     if (session == null) {
                         Slog.e(TAG,
@@ -4878,8 +4907,9 @@
             }
             // TODO(b/325515685): user data must be retrieved by a userId parameter
             final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
             if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
-                    userData.mBindingController.getCurMethodUid())) {
+                    bindingController.getCurMethodUid())) {
                 // Handle IME visibility when interactive changed before finishing the input to
                 // ensure we preserve the last state as possible.
                 final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
@@ -5124,7 +5154,8 @@
     @GuardedBy("ImfLock.class")
     void sendOnNavButtonFlagsChangedLocked() {
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod();
+        final var bindingController = userData.mBindingController;
+        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod == null) {
             // No need to send the data if the IME is not yet bound.
             return;
@@ -5558,7 +5589,7 @@
             //TODO(b/150843766): Check if Input Token is valid.
             final IBinder curHostInputToken;
             synchronized (ImfLock.class) {
-                if (displayId != mCurTokenDisplayId) {
+                if (displayId != getCurTokenDisplayIdLocked()) {
                     return false;
                 }
                 curHostInputToken = mAutofillController.getCurHostInputToken();
@@ -5610,6 +5641,7 @@
                 IAccessibilityInputMethodSession session, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
                 final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+                final var bindingController = userData.mBindingController;
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5632,8 +5664,8 @@
                     final InputBindResult res = new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
                             imeSession, accessibilityInputMethodSessions, /* channel= */ null,
-                            userData.mBindingController.getCurId(),
-                            userData.mBindingController.getSequenceNumber(),
+                            bindingController.getCurId(),
+                            bindingController.getSequenceNumber(),
                             /* isInputMethodSuppressingSpellChecker= */ false);
                     mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
                 }
@@ -5645,6 +5677,7 @@
                 @UserIdInt int userId) {
             synchronized (ImfLock.class) {
                 final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+                final var bindingController = userData.mBindingController;
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     if (DEBUG) {
@@ -5654,7 +5687,7 @@
                     // A11yManagerService unbinds the disabled accessibility service. We don't need
                     // to do it here.
                     mCurClient.mClient.onUnbindAccessibilityService(
-                            userData.mBindingController.getSequenceNumber(),
+                            bindingController.getSequenceNumber(),
                             accessibilityConnectionId);
                 }
                 // We only have sessions when we bound to an input method. Remove this session
@@ -5877,18 +5910,19 @@
             };
             mClientController.forAllClients(clientControllerDump);
             final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
             p.println("  mCurrentUserId=" + mCurrentUserId);
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq="
-                    + userData.mBindingController.getSequenceNumber());
+                    + bindingController.getSequenceNumber());
             p.println("  mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
             mImeBindingState.dump(/* prefix= */ "  ", p);
 
-            p.println("  mCurId=" + userData.mBindingController.getCurId()
-                    + " mHaveConnection=" + userData.mBindingController.hasMainConnection()
+            p.println("  mCurId=" + bindingController.getCurId()
+                    + " mHaveConnection=" + bindingController.hasMainConnection()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
-                    + userData.mBindingController.isVisibleBound());
+                    + bindingController.isVisibleBound());
 
             p.println("  mUserDataRepository=");
             // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@@ -5902,9 +5936,9 @@
             mUserDataRepository.forAllUserData(userDataDump);
 
             p.println("  mCurToken=" + getCurTokenLocked());
-            p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
+            p.println("  mCurTokenDisplayId=" + getCurTokenDisplayIdLocked());
             p.println("  mCurHostInputToken=" + mAutofillController.getCurHostInputToken());
-            p.println("  mCurIntent=" + userData.mBindingController.getCurIntent());
+            p.println("  mCurIntent=" + bindingController.getCurIntent());
             method = getCurMethodLocked();
             p.println("  mCurMethod=" + getCurMethodLocked());
             p.println("  mEnabledSession=" + mEnabledSession);
@@ -6395,7 +6429,8 @@
                         hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         final var userData = mUserDataRepository.getOrCreate(userId);
-                        userData.mBindingController.unbindCurrentMethod();
+                        final var bindingController = userData.mBindingController;
+                        bindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
                         var toDisable = settings.getEnabledInputMethodList();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
index 221309e..f06643df 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
@@ -23,6 +23,7 @@
 import android.util.ArrayMap;
 import android.view.inputmethod.InputMethodInfo;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -99,4 +100,37 @@
         }
         return updated ? InputMethodMap.of(newMethodMap) : this;
     }
+
+    /**
+     * Compares the given two {@link InputMethodMap} instances to see if they contain the same data
+     * or not.
+     *
+     * @param map1 {@link InputMethodMap} to be compared with
+     * @param map2 {@link InputMethodMap} to be compared with
+     * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data
+     */
+    @AnyThread
+    static boolean equals(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) {
+        if (map1 == map2) {
+            return true;
+        }
+        final int size = map1.size();
+        if (size != map2.size()) {
+            return false;
+        }
+        for (int i = 0; i < size; ++i) {
+            final var imi1 = map1.valueAt(i);
+            final var imeId = imi1.getId();
+            final var imi2 = map2.get(imeId);
+            if (imi2 == null) {
+                return false;
+            }
+            final var marshaled1 = InputMethodInfoUtils.marshal(imi1);
+            final var marshaled2 = InputMethodInfoUtils.marshal(imi2);
+            if (!Arrays.equals(marshaled1, marshaled2)) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 73647db..e1f8939 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2800,6 +2800,10 @@
             final String providerId = route.getProviderId();
             final MediaRoute2Provider provider = findProvider(providerId);
             if (provider == null) {
+                Slog.w(
+                        TAG,
+                        "Ignoring transferToRoute due to lack of matching provider for target: "
+                                + route);
                 return;
             }
             provider.transferToRoute(
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index a78b3a2..13429db 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -22,12 +22,14 @@
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 import static android.media.audio.Flags.focusExclusiveWithRecording;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
 
+import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -223,7 +225,10 @@
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
-                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+                    record -> mPackageManager.checkPermission(
+                            permission.RECEIVE_EMERGENCY_BROADCAST,
+                            record.getSbn().getPackageName()) == PERMISSION_GRANTED);
 
             return new StrategyAvalanche(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -231,14 +236,17 @@
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
-                    appStrategy);
+                    appStrategy, appStrategy.mExemptionProvider);
         } else {
             return new StrategyPerApp(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
-                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+                    record -> mPackageManager.checkPermission(
+                            permission.RECEIVE_EMERGENCY_BROADCAST,
+                            record.getSbn().getPackageName()) == PERMISSION_GRANTED);
         }
     }
 
@@ -1098,6 +1106,11 @@
         }
     }
 
+    // Returns true if a notification should be exempted from attenuation
+    private interface ExemptionProvider {
+        boolean isExempted(NotificationRecord record);
+    }
+
     @VisibleForTesting
     abstract static class PolitenessStrategy {
         static final int POLITE_STATE_DEFAULT = 0;
@@ -1128,8 +1141,10 @@
 
         protected boolean mIsActive = true;
 
+        protected final ExemptionProvider mExemptionProvider;
+
         public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
-                int volumeMuted) {
+                int volumeMuted, ExemptionProvider exemptionProvider) {
             mVolumeStates = new HashMap<>();
             mLastUpdatedTimestampByPackage = new HashMap<>();
 
@@ -1137,6 +1152,7 @@
             this.mTimeoutMuted = timeoutMuted;
             this.mVolumePolite = volumePolite / 100.0f;
             this.mVolumeMuted = volumeMuted / 100.0f;
+            this.mExemptionProvider = exemptionProvider;
         }
 
         abstract void onNotificationPosted(NotificationRecord record);
@@ -1294,8 +1310,8 @@
         private final int mMaxPostedForReset;
 
         public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
-                int volumeMuted, int maxPosted) {
-            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+                int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
 
             mNumPosted = new HashMap<>();
             mMaxPostedForReset = maxPosted;
@@ -1316,7 +1332,12 @@
 
             final String key = getChannelKey(record);
             @PolitenessState final int currState = getPolitenessState(record);
-            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+            @PolitenessState int nextState;
+            if (Flags.politeNotificationsAttnUpdate()) {
+                nextState = getNextState(currState, timeSinceLastNotif, record);
+            } else {
+                nextState = getNextState(currState, timeSinceLastNotif);
+            }
 
             // Reset to default state if number of posted notifications exceed this value when muted
             int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
@@ -1334,6 +1355,14 @@
             mVolumeStates.put(key, nextState);
         }
 
+        @PolitenessState int getNextState(@PolitenessState final int currState,
+                final long timeSinceLastNotif, final NotificationRecord record) {
+            if (mExemptionProvider.isExempted(record)) {
+                return POLITE_STATE_DEFAULT;
+            }
+            return getNextState(currState, timeSinceLastNotif);
+        }
+
         @Override
         public void onUserInteraction(final NotificationRecord record) {
             super.onUserInteraction(record);
@@ -1354,8 +1383,9 @@
         private long mLastAvalancheTriggerTimestamp = 0;
 
         StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
-                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
-            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy,
+                    ExemptionProvider exemptionProvider) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
 
             mTimeoutAvalanche = timeoutAvalanche;
             mAppStrategy = appStrategy;
@@ -1528,7 +1558,7 @@
                 return true;
             }
 
-            return false;
+            return mExemptionProvider.isExempted(record);
         }
 
         private boolean isAvalancheExempted(final NotificationRecord record) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 908b47d..472f228 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -37,7 +37,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
@@ -505,6 +505,7 @@
         // metadata file path for the new package.
         if (oldPkgSetting != null) {
             pkgSetting.setAppMetadataFilePath(null);
+            pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
         }
         // If the app metadata file path is not null then this is a system app with a preloaded app
         // metadata file on the system image. Do not reset the path and source if this is the
@@ -523,7 +524,7 @@
                 }
             } else if (Flags.aslInApkAppMetadataSource()) {
                 Map<String, PackageManager.Property> properties = pkg.getProperties();
-                if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+                if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
                     // ASL file extraction is done in post-install
                     pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
                     pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 121cf3f..fda8535 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4003,7 +4003,7 @@
                 final PackageMetrics.ComponentStateMetrics componentStateMetrics =
                         new PackageMetrics.ComponentStateMetrics(setting,
                                 UserHandle.getUid(userId, packageSetting.getAppId()),
-                                packageSetting.getEnabled(userId));
+                                packageSetting.getEnabled(userId), callingUid);
                 if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
                         callingPackage)) {
                     continue;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index b369f03..23ae983 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -19,7 +19,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
 import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
@@ -71,8 +71,12 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Build;
+import android.os.CancellationSignal;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -93,6 +97,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Base64;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.LogPrinter;
 import android.util.Printer;
@@ -147,11 +152,10 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.zip.GZIPInputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 /**
  * Class containing helper methods for the PackageManagerService.
@@ -1668,11 +1672,11 @@
             return true;
         }
         Map<String, Property> properties = pkg.getProperties();
-        if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+        if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
             return false;
         }
-        Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH);
-        if (!fileInAPkPathProperty.isString()) {
+        Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL);
+        if (!fileInApkProperty.isResourceId()) {
             return false;
         }
         if (isSystem && !appMetadataFile.getParentFile().exists()) {
@@ -1684,28 +1688,46 @@
                 return false;
             }
         }
-        String fileInApkPath = fileInAPkPathProperty.getString();
         List<AndroidPackageSplit> splits = pkg.getSplits();
+        AssetManager.Builder builder = new AssetManager.Builder();
         for (int i = 0; i < splits.size(); i++) {
-            try (ZipFile zipFile = new ZipFile(splits.get(i).getPath())) {
-                ZipEntry zipEntry = zipFile.getEntry(fileInApkPath);
-                if (zipEntry != null
-                        && (isSystem || zipEntry.getSize() <= getAppMetadataSizeLimit())) {
-                    try (InputStream in = zipFile.getInputStream(zipEntry)) {
-                        try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
-                            FileUtils.copy(in, out);
-                            Os.chmod(appMetadataFile.getAbsolutePath(),
-                                    APP_METADATA_FILE_ACCESS_MODE);
-                            return true;
+            try {
+                builder.addApkAssets(ApkAssets.loadFromPath(splits.get(i).getPath()));
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to load resources from APK " + splits.get(i).getPath());
+            }
+        }
+        AssetManager assetManager = builder.build();
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        displayMetrics.setToDefaults();
+        Resources res = new Resources(assetManager, displayMetrics, null);
+        AtomicBoolean copyFailed = new AtomicBoolean(false);
+        try (InputStream in = res.openRawResource(fileInApkProperty.getResourceId())) {
+            try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
+                if (isSystem) {
+                    FileUtils.copy(in, out);
+                } else {
+                    long sizeLimit = getAppMetadataSizeLimit();
+                    CancellationSignal signal = new CancellationSignal();
+                    FileUtils.copy(in, out, signal, Runnable::run, (long progress) -> {
+                        if (progress > sizeLimit) {
+                            copyFailed.set(true);
+                            signal.cancel();
                         }
-                    }
+                    });
                 }
-            } catch (Exception e) {
-                Slog.e(TAG, e.getMessage());
+                Os.chmod(appMetadataFile.getAbsolutePath(),
+                        APP_METADATA_FILE_ACCESS_MODE);
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, e.getMessage());
+            copyFailed.set(true);
+        } finally {
+            if (copyFailed.get()) {
                 appMetadataFile.delete();
             }
         }
-        return false;
+        return !copyFailed.get();
     }
 
     public static void linkFilesToOldDirs(@NonNull Installer installer,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7a36f6d..0a8b2b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -328,6 +328,8 @@
                     return runGetPrivappDenyPermissions();
                 case "get-oem-permissions":
                     return runGetOemPermissions();
+                case "get-signature-permission-allowlist":
+                    return runGetSignaturePermissionAllowlist();
                 case "trim-caches":
                     return runTrimCaches();
                 case "create-user":
@@ -2920,6 +2922,54 @@
         return 0;
     }
 
+    private int runGetSignaturePermissionAllowlist() {
+        final var partition = getNextArg();
+        if (partition == null) {
+            getErrPrintWriter().println("Error: no partition specified.");
+            return 1;
+        }
+        final var permissionAllowlist =
+                SystemConfig.getInstance().getPermissionAllowlist();
+        final ArrayMap<String, ArrayMap<String, Boolean>> allowlist;
+        switch (partition) {
+            case "system":
+                allowlist = permissionAllowlist.getSignatureAppAllowlist();
+                break;
+            case "vendor":
+                allowlist = permissionAllowlist.getVendorSignatureAppAllowlist();
+                break;
+            case "product":
+                allowlist = permissionAllowlist.getProductSignatureAppAllowlist();
+                break;
+            case "system-ext":
+                allowlist = permissionAllowlist.getSystemExtSignatureAppAllowlist();
+                break;
+            default:
+                getErrPrintWriter().println("Error: unknown partition: " + partition);
+                return 1;
+        }
+        final var ipw = new IndentingPrintWriter(getOutPrintWriter(), "  ");
+        final var allowlistSize = allowlist.size();
+        for (var allowlistIndex = 0; allowlistIndex < allowlistSize; allowlistIndex++) {
+            final var packageName = allowlist.keyAt(allowlistIndex);
+            final var permissions = allowlist.valueAt(allowlistIndex);
+            ipw.print("Package: ");
+            ipw.println(packageName);
+            ipw.increaseIndent();
+            final var permissionsSize = permissions.size();
+            for (var permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) {
+                final var permissionName = permissions.keyAt(permissionsIndex);
+                final var granted = permissions.valueAt(permissionsIndex);
+                if (granted) {
+                    ipw.print("Permission: ");
+                    ipw.println(permissionName);
+                }
+            }
+            ipw.decreaseIndent();
+        }
+        return 0;
+    }
+
     private int runTrimCaches() throws RemoteException {
         String size = getNextArg();
         if (size == null) {
@@ -4852,6 +4902,10 @@
         pw.println("  get-oem-permissions TARGET-PACKAGE");
         pw.println("    Prints all OEM permissions for a package.");
         pw.println("");
+        pw.println("  get-signature-permission-allowlist PARTITION");
+        pw.println("    Prints the signature permission allowlist for a partition.");
+        pw.println("    PARTITION is one of system, vendor, product and system-ext");
+        pw.println("");
         pw.println("  trim-caches DESIRED_FREE_SPACE [internal|UUID]");
         pw.println("    Trim cache files to reach the given free space.");
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 20598f9..2081f73 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -358,6 +358,7 @@
 
     public static class ComponentStateMetrics {
         public int mUid;
+        public int mCallingUid;
         public int mComponentOldState;
         public int mComponentNewState;
         public boolean mIsForWholeApp;
@@ -365,13 +366,14 @@
         @Nullable private String mClassName;
 
         ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
-                int componentOldState) {
+                int componentOldState, int callingUid) {
             mUid = uid;
             mComponentOldState = componentOldState;
             mComponentNewState = setting.getEnabledState();
             mIsForWholeApp = !setting.isComponent();
             mPackageName = setting.getPackageName();
             mClassName = setting.getClassName();
+            mCallingUid = callingUid;
         }
 
         public boolean isSameComponent(ActivityInfo activityInfo) {
@@ -412,14 +414,15 @@
                     componentStateMetrics.mComponentOldState,
                     componentStateMetrics.mComponentNewState,
                     isLauncher,
-                    componentStateMetrics.mIsForWholeApp);
+                    componentStateMetrics.mIsForWholeApp,
+                    componentStateMetrics.mCallingUid);
         }
     }
 
     private static void reportComponentStateChanged(int uid, int componentOldState,
-            int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+            int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) {
         FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
-                uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+                uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid);
     }
 
     private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 41d6288..8d6d774 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2142,7 +2142,8 @@
                 ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
                         originalComponentName);
                 if (unflattenOriginalComponentName == null) {
-                    Slog.d(TAG, "Incorrect component name from the attributes");
+                    Slog.wtf(TAG, "Incorrect component name: " + originalComponentName
+                            + " from the attributes");
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4ff345f..ebdca5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1924,16 +1924,20 @@
     private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
         if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
-            // TODO (b/308121702) It may be brittle to rely on user states to check profile state
-            int state;
-            synchronized (mUserStates) {
-                state = mUserStates.get(userId, UserState.STATE_NONE);
-            }
-            if (state != UserState.STATE_NONE) {
-                Slog.i(LOG_TAG,
-                        "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
-                                + " is still alive.");
-                return;
+            if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles()
+                    || getUserInfo(userId).isManagedProfile()) {
+                // TODO (b/308121702) It may be brittle to rely on user states to check managed
+                //  profile state
+                int state;
+                synchronized (mUserStates) {
+                    state = mUserStates.get(userId, UserState.STATE_NONE);
+                }
+                if (state != UserState.STATE_NONE) {
+                    Slog.i(LOG_TAG,
+                            "showConfirmCredentialToDisableQuietMode() called too early, managed "
+                                    + "user " + userId + " is still alive.");
+                    return;
+                }
             }
         }
         // otherwise, we show a profile challenge to trigger decryption of the user
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 49c4000..9a41551 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -463,11 +463,17 @@
     public static class BatteryStatsConfig {
         static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0;
         static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1;
-        static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD =
-                TimeUnit.HOURS.toMillis(1);
 
         private final int mFlags;
-        private SparseLongArray mPowerStatsThrottlePeriods;
+        private final Long mDefaultPowerStatsThrottlePeriod;
+        private final Map<String, Long> mPowerStatsThrottlePeriods;
+
+        @VisibleForTesting
+        public BatteryStatsConfig() {
+            mFlags = 0;
+            mDefaultPowerStatsThrottlePeriod = 0L;
+            mPowerStatsThrottlePeriods = Map.of();
+        }
 
         private BatteryStatsConfig(Builder builder) {
             int flags = 0;
@@ -478,6 +484,7 @@
                 flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
             }
             mFlags = flags;
+            mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod;
             mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods;
         }
 
@@ -485,7 +492,7 @@
          * Returns whether a BatteryStats reset should occur on unplug when the battery level is
          * high.
          */
-        boolean shouldResetOnUnplugHighBatteryLevel() {
+        public boolean shouldResetOnUnplugHighBatteryLevel() {
             return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG)
                     == RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG;
         }
@@ -494,14 +501,18 @@
          * Returns whether a BatteryStats reset should occur on unplug if the battery charge a
          * significant amount since it has been plugged in.
          */
-        boolean shouldResetOnUnplugAfterSignificantCharge() {
+        public boolean shouldResetOnUnplugAfterSignificantCharge() {
             return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG)
                     == RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
         }
 
-        long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) {
-            return mPowerStatsThrottlePeriods.get(powerComponent,
-                    DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD);
+        /**
+         * Returns  the minimum amount of time (in millis) to wait between passes
+         * of power stats collection for the specified power component.
+         */
+        public long getPowerStatsThrottlePeriod(String powerComponentName) {
+            return mPowerStatsThrottlePeriods.getOrDefault(powerComponentName,
+                    mDefaultPowerStatsThrottlePeriod);
         }
 
         /**
@@ -510,18 +521,19 @@
         public static class Builder {
             private boolean mResetOnUnplugHighBatteryLevel;
             private boolean mResetOnUnplugAfterSignificantCharge;
-            private SparseLongArray mPowerStatsThrottlePeriods;
+            public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD =
+                    TimeUnit.HOURS.toMillis(1);
+            public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU =
+                    TimeUnit.MINUTES.toMillis(1);
+            private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD;
+            private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>();
 
             public Builder() {
                 mResetOnUnplugHighBatteryLevel = true;
                 mResetOnUnplugAfterSignificantCharge = true;
-                mPowerStatsThrottlePeriods = new SparseLongArray();
-                setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
-                        TimeUnit.MINUTES.toMillis(1));
-                setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        TimeUnit.HOURS.toMillis(1));
-                setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
-                        TimeUnit.HOURS.toMillis(1));
+                setPowerStatsThrottlePeriodMillis(BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_CPU),
+                        DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU);
             }
 
             /**
@@ -553,9 +565,18 @@
              * Sets the minimum amount of time (in millis) to wait between passes
              * of power stats collection for the specified power component.
              */
-            public Builder setPowerStatsThrottlePeriodMillis(
-                    @BatteryConsumer.PowerComponent int powerComponent, long periodMs) {
-                mPowerStatsThrottlePeriods.put(powerComponent, periodMs);
+            public Builder setPowerStatsThrottlePeriodMillis(String powerComponentName,
+                    long periodMs) {
+                mPowerStatsThrottlePeriods.put(powerComponentName, periodMs);
+                return this;
+            }
+
+            /**
+             * Sets the minimum amount of time (in millis) to wait between passes
+             * of power stats collection for any components not configured explicitly.
+             */
+            public Builder setDefaultPowerStatsThrottlePeriodMillis(long periodMs) {
+                mDefaultPowerStatsThrottlePeriod = periodMs;
                 return this;
             }
         }
@@ -1586,8 +1607,7 @@
     protected final Constants mConstants;
 
     @VisibleForTesting
-    @GuardedBy("this")
-    protected BatteryStatsConfig mBatteryStatsConfig;
+    protected final BatteryStatsConfig mBatteryStatsConfig;
 
     @GuardedBy("this")
     private AlarmManager mAlarmManager = null;
@@ -1933,6 +1953,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName);
+        }
+
+        @Override
         public PowerStatsUidResolver getUidResolver() {
             return mPowerStatsUidResolver;
         }
@@ -11167,19 +11192,14 @@
                 mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator,
                 mClock, mMonotonicClock, traceDelegate, eventLogger);
 
-        mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector,
-                mBatteryStatsConfig.getPowerStatsThrottlePeriod(
-                        BatteryConsumer.POWER_COMPONENT_CPU));
+        mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
         mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
 
         mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
-                mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
-                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
+                mPowerStatsCollectorInjector);
         mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
 
-        mWifiPowerStatsCollector = new WifiPowerStatsCollector(
-                mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
-                BatteryConsumer.POWER_COMPONENT_WIFI));
+        mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector);
         mWifiPowerStatsCollector.addConsumer(this::recordPowerStats);
 
         mStartCount++;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index f53a1b0..b5ef67b 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -59,6 +59,7 @@
         KernelCpuStatsReader getKernelCpuStatsReader();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
         IntSupplier getVoltageSupplier();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
 
         default int getDefaultCpuPowerBrackets() {
             return DEFAULT_CPU_POWER_BRACKETS;
@@ -94,9 +95,11 @@
     private int mLastVoltageMv;
     private long[] mLastConsumedEnergyUws;
 
-    public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
-                injector.getClock());
+    CpuPowerStatsCollector(Injector injector) {
+        super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_CPU)),
+                injector.getUidResolver(), injector.getClock());
         mInjector = injector;
     }
 
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 7bc6817..a96e01b 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -73,6 +73,7 @@
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
         IntSupplier getVoltageSupplier();
@@ -104,8 +105,11 @@
     private long mLastCallDuration;
     private long mLastScanDuration;
 
-    public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+    MobileRadioPowerStatsCollector(Injector injector) {
+        super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)),
+                injector.getUidResolver(),
                 injector.getClock());
         mInjector = injector;
     }
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 6321053..bd04199 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -56,6 +56,7 @@
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
         IntSupplier getVoltageSupplier();
@@ -92,9 +93,11 @@
     private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
     private long mLastWifiActiveDuration;
 
-    public WifiPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
-                injector.getClock());
+    WifiPowerStatsCollector(Injector injector) {
+        super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_WIFI)),
+                injector.getUidResolver(), injector.getClock());
         mInjector = injector;
     }
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 3b9ad19..c1b825b 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -836,9 +836,7 @@
                 registerEventListeners();
             });
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-            if (true) {
-                initNetworkStatsManager();
-            }
+            initNetworkStatsManager();
             BackgroundThread.getHandler().post(() -> {
                 // Network stats related pullers can only be initialized after service is ready.
                 initAndRegisterNetworkStatsPullers();
@@ -859,9 +857,6 @@
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
-        if (false) {
-            initNetworkStatsManager();
-        }
 
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1043,10 +1038,8 @@
      */
     @NonNull
     private NetworkStatsManager getNetworkStatsManager() {
-        if (true) {
-            if (mNetworkStatsManager == null) {
-                throw new IllegalStateException("NetworkStatsManager is not ready");
-            }
+        if (mNetworkStatsManager == null) {
+            throw new IllegalStateException("NetworkStatsManager is not ready");
         }
         return mNetworkStatsManager;
     }
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index f6afc52..3393d3e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,7 +150,11 @@
     @Override
     public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
             DisplayInfo[] displayInfos) {
-        mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+        if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
+            onWindowInfosChangedInternal(windowHandles, displayInfos);
+        } else {
+            mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+        }
     }
 
     private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b32a30..76e7f53 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -339,7 +339,6 @@
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
@@ -2123,14 +2122,14 @@
         if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
             // When the stable configuration is the default behavior, override for the legacy apps
             // without forward override flag.
-            mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+            mResolveConfigHint.mUseOverrideInsetsForConfig =
                     !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
                             && !info.isChangeEnabled(
                                     OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
         } else {
             // When the stable configuration is not the default behavior, forward overriding the
             // listed apps.
-            mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+            mResolveConfigHint.mUseOverrideInsetsForConfig =
                     info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
         }
 
@@ -5682,6 +5681,8 @@
                 } else if (mTransitionController.inFinishingTransition(this)) {
                     mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
                 }
+            } else {
+                mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED;
             }
             return;
         }
@@ -6529,8 +6530,8 @@
             // and the token could be null.
             return;
         }
-        if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
-            r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+        if (r.mDisplayContent.mActivityRefresher != null) {
+            r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
         }
     }
 
@@ -8490,7 +8491,7 @@
         mCompatDisplayInsets =
                 new CompatDisplayInsets(
                         mDisplayContent, this, letterboxedContainerBounds,
-                        mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+                        mResolveConfigHint.mUseOverrideInsetsForConfig);
     }
 
     private void clearSizeCompatModeAttributes() {
@@ -8570,8 +8571,6 @@
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
 
-        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
-
         // Bubble activities should always fill their parent and should not be letterboxed.
         final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
                 && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
@@ -8671,6 +8670,8 @@
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
         }
 
+        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
         logAppCompatState();
     }
 
@@ -8689,14 +8690,13 @@
         if (mDisplayContent == null) {
             return;
         }
-        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
         int rotation = newParentConfiguration.windowConfiguration.getRotation();
         if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
             rotation = mDisplayContent.getRotation();
         }
-        if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds
-                || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
-                || rotation == ROTATION_UNDEFINED) {
+        if (!mResolveConfigHint.mUseOverrideInsetsForConfig
+                || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()
+                || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) {
             // If the insets configuration decoupled logic is not enabled for the app, or the app
             // already has a compat override, or the context doesn't contain enough info to
             // calculate the override, skip the override.
@@ -8713,53 +8713,7 @@
         }
 
         // Override starts here.
-        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
-                : mDisplayContent.mBaseDisplayWidth;
-        final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
-                : mDisplayContent.mBaseDisplayHeight;
-        final  Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
-                .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
-        // This should be the only place override the configuration for ActivityRecord. Override
-        // the value if not calculated yet.
-        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-        if (outAppBounds == null || outAppBounds.isEmpty()) {
-            inOutConfig.windowConfiguration.setAppBounds(parentBounds);
-            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(nonDecorInsets);
-        }
-        float density = inOutConfig.densityDpi;
-        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
-            density = newParentConfiguration.densityDpi;
-        }
-        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-            final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
-            inOutConfig.screenWidthDp = overrideScreenWidthDp;
-        }
-        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-            final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
-            inOutConfig.screenHeightDp = overrideScreenHeightDp;
-        }
-        if (inOutConfig.smallestScreenWidthDp
-                == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
-                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // For the case of PIP transition and multi-window environment, the
-            // smallestScreenWidthDp is handled already. Override only if the app is in
-            // fullscreen.
-            final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
-            mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
-                    mDisplayContent.getDisplayMetrics().density,
-                    inOutConfig, true /* overrideConfig */);
-        }
-
-        // It's possible that screen size will be considered in different orientation with or
-        // without considering the system bar insets. Override orientation as well.
-        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
-            inOutConfig.orientation =
-                    (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
-                            ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
-        }
+        computeConfigByResolveHint(inOutConfig, newParentConfiguration);
     }
 
     private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@@ -9015,7 +8969,7 @@
         if (mDisplayContent == null) {
             return true;
         }
-        if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds) {
+        if (!mResolveConfigHint.mUseOverrideInsetsForConfig) {
             // No insets should be considered any more.
             return true;
         }
@@ -9034,7 +8988,7 @@
         final Task task = getTask();
         task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
                 outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
-                mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+                mResolveConfigHint.mUseOverrideInsetsForConfig);
         final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
@@ -9091,7 +9045,7 @@
                 getResolvedOverrideConfiguration().windowConfiguration.getBounds();
         final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
                 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
-        final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+        final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
                 ? stableBoundsOrientation : newParentConfig.orientation;
 
         // If the activity requires a different orientation (either by override or activityInfo),
@@ -9116,7 +9070,7 @@
             return;
         }
 
-        final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+        final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForConfig
                 ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
         // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
         // bounds or stable bounds to unify aspect ratio logic.
@@ -10042,7 +9996,7 @@
             } else {
                 scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
             }
-            notifyDisplayCompatPolicyAboutConfigurationChange(
+            notifyActivityRefresherAboutConfigurationChange(
                     mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
             return true;
         }
@@ -10109,18 +10063,18 @@
         } else {
             scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
         }
-        notifyDisplayCompatPolicyAboutConfigurationChange(
+        notifyActivityRefresherAboutConfigurationChange(
                 mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
         return true;
     }
 
-    private void notifyDisplayCompatPolicyAboutConfigurationChange(
+    private void notifyActivityRefresherAboutConfigurationChange(
             Configuration newConfig, Configuration lastReportedConfig) {
-        if (mDisplayContent.mDisplayRotationCompatPolicy == null
+        if (mDisplayContent.mActivityRefresher == null
                 || !shouldBeResumed(/* activeActivity */ null)) {
             return;
         }
-        mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+        mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
                 this, newConfig, lastReportedConfig);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
new file mode 100644
index 0000000..23a9708
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
+ *
+ * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
+ * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
+ * through either stop or pause and then resume, based on the global config and per-app override.
+ */
+class ActivityRefresher {
+    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+    // client process may not always report the event back to the server, such as process is
+    // crashed or got killed.
+    private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L;
+
+    @NonNull private final WindowManagerService mWmService;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();
+
+    ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
+        mWmService = wmService;
+        mHandler = handler;
+    }
+
+    void addEvaluator(@NonNull Evaluator evaluator) {
+        mEvaluators.add(evaluator);
+    }
+
+    void removeEvaluator(@NonNull Evaluator evaluator) {
+        mEvaluators.remove(evaluator);
+    }
+
+    /**
+     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+     * camera preview and can lead to sideways or stretching issues persisting even after force
+     * rotation.
+     */
+    void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+        if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+            return;
+        }
+
+        final boolean cycleThroughStop =
+                mWmService.mLetterboxConfiguration
+                        .isCameraCompatRefreshCycleThroughStopEnabled()
+                        && !activity.mLetterboxUiController
+                        .shouldRefreshActivityViaPauseForCameraCompat();
+
+        activity.mLetterboxUiController.setIsRefreshRequested(true);
+        ProtoLog.v(WM_DEBUG_STATES,
+                "Refreshing activity for freeform camera compatibility treatment, "
+                        + "activityRecord=%s", activity);
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
+                activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+                activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+        try {
+            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+            mHandler.postDelayed(() -> {
+                synchronized (mWmService.mGlobalLock) {
+                    onActivityRefreshed(activity);
+                }
+            }, REFRESH_CALLBACK_TIMEOUT_MS);
+        } catch (RemoteException e) {
+            activity.mLetterboxUiController.setIsRefreshRequested(false);
+        }
+    }
+
+    boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
+        return activity.mLetterboxUiController.isRefreshRequested();
+    }
+
+    void onActivityRefreshed(@NonNull ActivityRecord activity) {
+        // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
+        //  state?
+        activity.mLetterboxUiController.setIsRefreshRequested(false);
+    }
+
+    private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+        return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
+                ((Evaluator) evaluator)
+                        .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
+    }
+
+    /**
+     * Interface for classes that would like to refresh the recently updated activity, based on the
+     * configuration change.
+     */
+    interface Evaluator {
+        boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+                @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5079ec1..e49cb38 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -478,6 +478,8 @@
     final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     @Nullable
     final CameraStateMonitor mCameraStateMonitor;
+    @Nullable
+    final ActivityRefresher mActivityRefresher;
 
     DisplayFrames mDisplayFrames;
     final DisplayUpdater mDisplayUpdater;
@@ -1233,13 +1235,15 @@
                 mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
         if (shouldCreateDisplayRotationCompatPolicy) {
             mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
+            mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
             mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
-                    this, mWmService.mH, mCameraStateMonitor);
+                    this, mCameraStateMonitor, mActivityRefresher);
 
             mCameraStateMonitor.startListeningToCameraState();
         } else {
             // These are to satisfy the `final` check.
             mCameraStateMonitor = null;
+            mActivityRefresher = null;
             mDisplayRotationCompatPolicy = null;
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index eacf9a3..e0cc064 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -18,8 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -32,19 +30,14 @@
 import static android.view.Display.TYPE_INTERNAL;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.app.servertransaction.RefreshCallbackItem;
-import android.app.servertransaction.ResumeActivityItem;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.RemoteException;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -64,48 +57,38 @@
  * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
  */
  // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener,
+        ActivityRefresher.Evaluator {
 
-    // Delay for updating display rotation after Camera connection is closed. Needed to avoid
-    // rotation flickering when an app is flipping between front and rear cameras or when size
-    // compat mode is restarted.
-    // TODO(b/263114289): Consider associating this delay with a specific activity so that if
-    // the new non-camera activity started on top of the camer one we can rotate faster.
-    private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
-    // Delay for updating display rotation after Camera connection is opened. This delay is
-    // selected to be long enough to avoid conflicts with transitions on the app's side.
-    // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
-    // is flipping between front and rear cameras (in case requested orientation changes at
-    // runtime at the same time) or when size compat mode is restarted.
-    private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
-            CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
-    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
-    // client process may not always report the event back to the server, such as process is
-    // crashed or got killed.
-    private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
-
+    @NonNull
     private final DisplayContent mDisplayContent;
+    @NonNull
     private final WindowManagerService mWmService;
+    @NonNull
     private final CameraStateMonitor mCameraStateMonitor;
-    private final Handler mHandler;
+    @NonNull
+    private final ActivityRefresher mActivityRefresher;
 
     @ScreenOrientation
     private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
 
-    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler,
-            @NonNull CameraStateMonitor cameraStateMonitor) {
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent,
+            @NonNull CameraStateMonitor cameraStateMonitor,
+            @NonNull ActivityRefresher activityRefresher) {
         // This constructor is called from DisplayContent constructor. Don't use any fields in
         // DisplayContent here since they aren't guaranteed to be set.
-        mHandler = handler;
         mDisplayContent = displayContent;
         mWmService = displayContent.mWmService;
         mCameraStateMonitor = cameraStateMonitor;
         mCameraStateMonitor.addCameraStateListener(this);
+        mActivityRefresher = activityRefresher;
+        mActivityRefresher.addEvaluator(this);
     }
 
     /** Releases camera state listener. */
     void dispose() {
         mCameraStateMonitor.removeCameraStateListener(this);
+        mActivityRefresher.removeEvaluator(this);
     }
 
     /**
@@ -169,47 +152,6 @@
     }
 
     /**
-     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
-     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
-     * camera preview and can lead to sideways or stretching issues persisting even after force
-     * rotation.
-     */
-    void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
-            Configuration lastReportedConfig) {
-        if (!isTreatmentEnabledForDisplay()
-                || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
-                || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
-            return;
-        }
-        boolean cycleThroughStop =
-                mWmService.mLetterboxConfiguration
-                        .isCameraCompatRefreshCycleThroughStopEnabled()
-                && !activity.mLetterboxUiController
-                        .shouldRefreshActivityViaPauseForCameraCompat();
-        try {
-            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
-            ProtoLog.v(WM_DEBUG_STATES,
-                    "Refreshing activity for camera compatibility treatment, "
-                            + "activityRecord=%s", activity);
-            final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
-                    activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
-            final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
-                    activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
-            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
-                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
-            mHandler.postDelayed(
-                    () -> onActivityRefreshed(activity),
-                    REFRESH_CALLBACK_TIMEOUT_MS);
-        } catch (RemoteException e) {
-            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
-        }
-    }
-
-    void onActivityRefreshed(@NonNull ActivityRecord activity) {
-        activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
-    }
-
-    /**
      * Notifies that animation in {@link ScreenRotationAnimation} has finished.
      *
      * <p>This class uses this signal as a trigger for notifying the user about forced rotation
@@ -276,14 +218,16 @@
 
     // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
     // treatment is enabled
-    private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
-            Configuration lastReportedConfig) {
+    @Override
+    public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
         final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation()
                 != lastReportedConfig.windowConfiguration.getDisplayRotation());
-        return (displayRotationChanged
-                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed())
+        return isTreatmentEnabledForDisplay()
                 && isTreatmentEnabledForActivity(activity)
-                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
+                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && (displayRotationChanged
+                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
     }
 
     /**
@@ -310,7 +254,6 @@
                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
 
-
     /**
      * Whether camera compat treatment is applicable for the given activity.
      *
@@ -429,6 +372,6 @@
                 || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
             return false;
         }
-        return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+        return mActivityRefresher.isActivityRefreshing(topActivity);
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 4400ed2..2288fe9 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -198,7 +198,7 @@
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
                 if (mPendingControlTarget != mControlTarget) {
-                    updateControlForTarget(mPendingControlTarget, true /* force */);
+                    mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 57827c5..16d7b4f 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -260,7 +260,7 @@
     // Whether activity "refresh" was requested but not finished in
     // ActivityRecord#activityResumedLocked following the camera compat force rotation in
     // DisplayRotationCompatPolicy.
-    private boolean mIsRefreshAfterRotationRequested;
+    private boolean mIsRefreshRequested;
 
     @NonNull
     private final OptProp mIgnoreRequestedOrientationOptProp;
@@ -571,15 +571,14 @@
     }
 
     /**
-     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
-     * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
      */
-    boolean isRefreshAfterRotationRequested() {
-        return mIsRefreshAfterRotationRequested;
+    boolean isRefreshRequested() {
+        return mIsRefreshRequested;
     }
 
-    void setIsRefreshAfterRotationRequested(boolean isRequested) {
-        mIsRefreshAfterRotationRequested = isRequested;
+    void setIsRefreshRequested(boolean isRequested) {
+        mIsRefreshRequested = isRequested;
     }
 
     boolean isOverrideRespectRequestedOrientationEnabled() {
@@ -1068,7 +1067,7 @@
      * thin letteboxing
      */
     boolean allowVerticalReachabilityForThinLetterbox() {
-        if (!Flags.disableThinLetterboxingReachability()) {
+        if (!Flags.disableThinLetterboxingPolicy()) {
             return true;
         }
         // When the flag is enabled we allow vertical reachability only if the
@@ -1081,7 +1080,7 @@
      * thin letteboxing
      */
     boolean allowHorizontalReachabilityForThinLetterbox() {
-        if (!Flags.disableThinLetterboxingReachability()) {
+        if (!Flags.disableThinLetterboxingPolicy()) {
             return true;
         }
         // When the flag is enabled we allow horizontal reachability only if the
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a444c96..26e4eaa 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -40,6 +40,8 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.USER_NULL;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
@@ -2222,7 +2224,7 @@
     static class ConfigOverrideHint {
         @Nullable DisplayInfo mTmpOverrideDisplayInfo;
         @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
-        boolean mUseOverrideInsetsForStableBounds;
+        boolean mUseOverrideInsetsForConfig;
     }
 
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@@ -2255,11 +2257,11 @@
             @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
         DisplayInfo overrideDisplayInfo = null;
         ActivityRecord.CompatDisplayInsets compatInsets = null;
-        boolean useOverrideInsetsForStableBounds = false;
+        boolean useOverrideInsetsForConfig = false;
         if (overrideHint != null) {
             overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
             compatInsets = overrideHint.mTmpCompatInsets;
-            useOverrideInsetsForStableBounds = overrideHint.mUseOverrideInsetsForStableBounds;
+            useOverrideInsetsForConfig = overrideHint.mUseOverrideInsetsForConfig;
             if (overrideDisplayInfo != null) {
                 // Make sure the screen related configs can be computed by the provided
                 // display info.
@@ -2323,6 +2325,7 @@
             }
         }
 
+        boolean insetsOverrideApplied = false;
         if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
                 || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
             if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
@@ -2339,7 +2342,7 @@
                 // The non decor inset are areas that could never be removed in Honeycomb. See
                 // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
                 calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
-                        useOverrideInsetsForStableBounds);
+                        useOverrideInsetsForConfig);
             } else {
                 // Apply the given non-decor and stable insets to calculate the corresponding bounds
                 // for screen size of configuration.
@@ -2356,8 +2359,21 @@
                     intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
                             compatInsets.mStableInsets[rotation]);
                     outAppBounds.set(mTmpNonDecorBounds);
+                } else if (useOverrideInsetsForConfig) {
+                    final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+                    final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+                            : mDisplayContent.mBaseDisplayWidth;
+                    final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+                            : mDisplayContent.mBaseDisplayHeight;
+                    final DisplayPolicy.DecorInsets.Info decorInsets = mDisplayContent
+                            .getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+                    mTmpStableBounds.set(outAppBounds);
+                    mTmpStableBounds.inset(decorInsets.mOverrideConfigInsets);
+                    outAppBounds.inset(decorInsets.mOverrideNonDecorInsets);
+                    mTmpNonDecorBounds.set(outAppBounds);
+                    // Record the override apply to avoid duplicated check.
+                    insetsOverrideApplied = true;
                 } else {
-                    // Set to app bounds because it excludes decor insets.
                     mTmpNonDecorBounds.set(outAppBounds);
                     mTmpStableBounds.set(outAppBounds);
                 }
@@ -2399,6 +2415,11 @@
                     // from the parent task would result in applications loaded wrong resource.
                     inOutConfig.smallestScreenWidthDp =
                             Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
+                } else if (insetsOverrideApplied) {
+                    // The smallest width should also consider insets. If the insets are overridden,
+                    // use the overridden value.
+                    inOutConfig.smallestScreenWidthDp =
+                            Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
                 }
                 // otherwise, it will just inherit
             }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 74ca9ad..97f1e19 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -122,7 +122,6 @@
     jmethodID interceptMotionBeforeQueueingNonInteractive;
     jmethodID interceptKeyBeforeDispatching;
     jmethodID dispatchUnhandledKey;
-    jmethodID onPointerDisplayIdChanged;
     jmethodID onPointerDownOutsideFocus;
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
@@ -786,12 +785,6 @@
     } // release lock
     mInputManager->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // Notify the system.
-    JNIEnv* env = jniEnv();
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
-                        position.x, position.y);
-    checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
 }
 
 void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -2933,9 +2926,6 @@
             "dispatchUnhandledKey",
             "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
 
-    GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
-                  "(IFF)V");
-
     GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz,
                   "notifyStickyModifierStateChanged", "(II)V");
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d114337..d733762 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -217,7 +217,7 @@
     <V> void setLocalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
-            @Nullable PolicyValue<V> value,
+            @NonNull PolicyValue<V> value,
             int userId,
             boolean skipEnforcePolicy) {
         Objects.requireNonNull(policyDefinition);
@@ -313,6 +313,7 @@
         }
         updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
         write();
+        applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
@@ -400,7 +401,7 @@
      * else remove the policy from child.
      */
     private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
-            EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) {
+            EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) {
         if (policyDefinition.isInheritable()) {
             Binder.withCleanCallingIdentity(() -> {
                 List<UserInfo> userInfos = mUserManager.getProfiles(userId);
@@ -1742,14 +1743,17 @@
         }
     }
 
-    <V> void reapplyAllPoliciesLocked() {
+    <V> void reapplyAllPoliciesOnBootLocked() {
         for (PolicyKey policy : mGlobalPolicies.keySet()) {
             PolicyState<?> policyState = mGlobalPolicies.get(policy);
             // Policy definition and value will always be of the same type
             PolicyDefinition<V> policyDefinition =
                     (PolicyDefinition<V>) policyState.getPolicyDefinition();
-            PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
-            enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+            if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+                PolicyValue<V> policyValue =
+                        (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+                enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+            }
         }
         for (int i = 0; i < mLocalPolicies.size(); i++) {
             int userId = mLocalPolicies.keyAt(i);
@@ -1758,10 +1762,11 @@
                 // Policy definition and value will always be of the same type
                 PolicyDefinition<V> policyDefinition =
                         (PolicyDefinition<V>) policyState.getPolicyDefinition();
-                PolicyValue<V> policyValue =
-                        (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
-                enforcePolicy(policyDefinition, policyValue, userId);
-
+                if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+                    PolicyValue<V> policyValue =
+                            (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+                    enforcePolicy(policyDefinition, policyValue, userId);
+                }
             }
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b93d21..85d2a0d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3351,7 +3351,7 @@
                 break;
             case SystemService.PHASE_SYSTEM_SERVICES_READY:
                 synchronized (getLockObject()) {
-                    mDevicePolicyEngine.reapplyAllPoliciesLocked();
+                    mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked();
                 }
                 break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
@@ -11443,7 +11443,7 @@
             }
             setBackwardsCompatibleAppRestrictions(
                     caller, packageName, restrictions, caller.getUserHandle());
-        } else if (Flags.dmrhCanSetAppRestriction()) {
+        } else if (Flags.dmrhSetAppRestrictions()) {
             final boolean isRoleHolder;
             if (who != null) {
                 // DO or PO
@@ -11484,10 +11484,6 @@
                             new BundlePolicyValue(restrictions),
                             affectedUserId);
                 }
-                Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
-                changeIntent.setPackage(packageName);
-                changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
             } else {
                 mInjector.binderWithCleanCallingIdentity(() -> {
                     mUserManager.setApplicationRestrictions(packageName, restrictions,
@@ -12845,7 +12841,7 @@
                 return Bundle.EMPTY;
             }
             return policies.get(enforcingAdmin).getValue();
-        } else if (Flags.dmrhCanSetAppRestriction()) {
+        } else if (Flags.dmrhSetAppRestrictions()) {
             final boolean isRoleHolder;
             if (who != null) {
                 // Caller is DO or PO. They cannot call this on parent
@@ -15770,8 +15766,13 @@
                             PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
                             userId);
             List<Bundle> restrictions = new ArrayList<>();
-            for (EnforcingAdmin admin : policies.keySet()) {
-                restrictions.add(policies.get(admin).getValue());
+            for (PolicyValue<Bundle> policyValue: policies.values()) {
+                Bundle value = policyValue.getValue();
+                // Probably not necessary since setApplicationRestrictions only sets non-empty
+                // Bundle, but just in case.
+                if (value != null && !value.isEmpty()) {
+                    restrictions.add(value);
+                }
             }
 
             return mInjector.binderWithCleanCallingIdentity(() -> {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 1000bfa..cbd2847 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -52,7 +52,18 @@
     EnterpriseSpecificIdCalculator(Context context) {
         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
         Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
-        mImei = telephonyService.getImei(0);
+
+        String imei;
+        try {
+            imei = telephonyService.getImei(0);
+        } catch (UnsupportedOperationException doesNotSupportGms) {
+            // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
+            // However that runs the risk of changing a device's existing ESID if on these devices
+            // telephonyService.getImei() actually returns non-null even when the device does not
+            // declare FEATURE_TELEPHONY_GSM.
+            imei = null;
+        }
+        mImei = imei;
         String meid;
         try {
             meid = telephonyService.getMeid(0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8d980b5..8bec384 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -51,6 +51,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 final class PolicyDefinition<V> {
@@ -82,6 +83,10 @@
     // them.
     private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4;
 
+    // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the
+    // callback in other cases such as device reboots.
+    private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5;
+
     private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
             List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
 
@@ -231,11 +236,11 @@
                     // Don't need to take in a resolution mechanism since its never used, but might
                     // need some refactoring to not always assume a non-null mechanism.
                     new MostRecent<>(),
-                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
-                    // Application restrictions are now stored and retrieved from DPMS, so no
-                    // enforcing is required, however DPMS calls into UM to set restrictions for
-                    // backwards compatibility.
-                    (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true,
+                    // Only invoke the enforcement callback during policy change and not other state
+                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE
+                            | POLICY_FLAG_NON_COEXISTABLE_POLICY
+                            | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED,
+                    PolicyEnforcerCallbacks::setApplicationRestrictions,
                     new BundlePolicySerializer());
 
     /**
@@ -581,6 +586,10 @@
         return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0;
     }
 
+    boolean shouldSkipEnforcementIfNotChanged() {
+        return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0;
+    }
+
     @Nullable
     PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
         return mResolutionMechanism.resolve(adminsPolicy);
@@ -610,7 +619,7 @@
      * {@link Object#equals} implementation.
      */
     private PolicyDefinition(
-            PolicyKey key,
+            @NonNull  PolicyKey key,
             ResolutionMechanism<V> resolutionMechanism,
             QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
@@ -622,11 +631,12 @@
      * {@link Object#equals} and {@link Object#hashCode()} implementation.
      */
     private PolicyDefinition(
-            PolicyKey policyKey,
+            @NonNull  PolicyKey policyKey,
             ResolutionMechanism<V> resolutionMechanism,
             int policyFlags,
             QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
+        Objects.requireNonNull(policyKey);
         mPolicyKey = policyKey;
         mResolutionMechanism = resolutionMechanism;
         mPolicyFlags = policyFlags;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 09eef45..04d277e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -37,11 +37,13 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -172,6 +174,29 @@
         return true;
     }
 
+
+    /**
+     * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing
+     * it to UMS) is required. Only need to send broadcast to the target user here as we rely on
+     * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple
+     * profiles. The broadcast should only be sent when an application restriction is set, so we
+     * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
+     * when the policy is set, and not during system boot or other situations.
+     */
+    static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
+            PolicyKey policyKey) {
+        Binder.withCleanCallingIdentity(() -> {
+            PackagePolicyKey key = (PackagePolicyKey) policyKey;
+            String packageName = key.getPackageName();
+            Objects.requireNonNull(packageName);
+            Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+            changeIntent.setPackage(packageName);
+            changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
+        });
+        return true;
+    }
+
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
new file mode 100644
index 0000000..50804da
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class InputMethodInfoUtilsTest {
+
+    @Test
+    public void testMarshalSameObject() {
+        final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final byte[] buf = InputMethodInfoUtils.marshal(imi);
+
+        assertArrayEquals("The same value must be returned when called multiple times",
+                buf, InputMethodInfoUtils.marshal(imi));
+        assertArrayEquals("The same value must be returned when called multiple times",
+                buf, InputMethodInfoUtils.marshal(imi));
+    }
+
+    @Test
+    public void testMarshalDifferentObjects() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(0));
+
+        assertFalse("Different inputs must yield different byte patterns", Arrays.equals(
+                InputMethodInfoUtils.marshal(imi1), InputMethodInfoUtils.marshal(imi2)));
+    }
+
+    @NonNull
+    private static <T> T readTypedObject(byte[] data, @NonNull Parcelable.Creator<T> creator) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.unmarshall(data, 0, data.length);
+            parcel.setDataPosition(0);
+            return Objects.requireNonNull(parcel.readTypedObject(creator));
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void testUnmarshalSameObject() {
+        final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final var cloned = readTypedObject(InputMethodInfoUtils.marshal(imi),
+                InputMethodInfo.CREATOR);
+        assertEquals(imi.getPackageName(), cloned.getPackageName());
+        assertEquals(imi.getSubtypeCount(), cloned.getSubtypeCount());
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 28a99f2f..2bbd3c0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -127,6 +127,7 @@
     protected IInputMethodInvoker mMockInputMethodInvoker;
     protected InputMethodManagerService mInputMethodManagerService;
     protected ServiceThread mServiceThread;
+    protected ServiceThread mPackageMonitorThread;
     protected boolean mIsLargeScreen;
     private InputManagerGlobal.TestSession mInputManagerGlobalSession;
 
@@ -222,11 +223,16 @@
 
         mServiceThread =
                 new ServiceThread(
-                        "TestServiceThread",
-                        Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
-                        false);
+                        "immstest1",
+                        Process.THREAD_PRIORITY_FOREGROUND,
+                        true /* allowIo */);
+        mPackageMonitorThread =
+                new ServiceThread(
+                        "immstest2",
+                        Process.THREAD_PRIORITY_FOREGROUND,
+                        true /* allowIo */);
         mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
-                unusedUserId -> mMockInputMethodBindingController);
+                mPackageMonitorThread, unusedUserId -> mMockInputMethodBindingController);
         spyOn(mInputMethodManagerService);
 
         // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
@@ -259,6 +265,10 @@
             mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
         }
 
+        if (mPackageMonitorThread != null) {
+            mPackageMonitorThread.quitSafely();
+        }
+
         if (mServiceThread != null) {
             mServiceThread.quitSafely();
         }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
new file mode 100644
index 0000000..5e3bc56
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID3;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+public final class InputMethodMapTest {
+
+    @NonNull
+    private static InputMethodMap toMap(InputMethodInfo... list) {
+        final ArrayMap<String, InputMethodInfo> map = new ArrayMap<>();
+        for (var imi : list) {
+            map.put(imi.getId(), imi);
+        }
+        return InputMethodMap.of(map);
+    }
+
+    @Test
+    public void testEqualsSameObject() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        final var map = toMap(imi1, imi2);
+        assertTrue("Must return true for the same instance",
+                InputMethodMap.equals(map, map));
+    }
+
+    @Test
+    public void testEqualsEquivalentObject() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        assertTrue("Must return true for the equivalent instances",
+                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi2)));
+
+        assertTrue("Must return true for the equivalent instances",
+                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi2, imi1)));
+    }
+
+    @Test
+    public void testEqualsDifferentKeys() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3));
+        assertFalse("Must return false if keys are different",
+                InputMethodMap.equals(toMap(imi1), toMap(imi1, imi2)));
+        assertFalse("Must return false if keys are different",
+                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1)));
+        assertFalse("Must return false if keys are different",
+                InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi3)));
+    }
+
+    @Test
+    public void testEqualsDifferentValues() {
+        final var imi1_without_subtypes =
+                createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi1_with_subtypes =
+                createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        assertFalse("Must return false if values are different",
+                InputMethodMap.equals(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes)));
+        assertFalse("Must return false if values are different",
+                InputMethodMap.equals(
+                        toMap(imi1_without_subtypes, imi2),
+                        toMap(imi1_with_subtypes, imi2)));
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
new file mode 100644
index 0000000..c51ff87f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
@@ -0,0 +1,101 @@
+/*
+ * 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.inputmethod;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+public final class TestUtils {
+    /**
+     * {@link ComponentName} for fake {@link InputMethodInfo}.
+     */
+    @NonNull
+    public static final ComponentName TEST_IME_ID1 = Objects.requireNonNull(
+            ComponentName.unflattenFromString("com.android.test.testime1/.InputMethod"));
+
+    /**
+     * {@link ComponentName} for fake {@link InputMethodInfo}.
+     */
+    @NonNull
+    public static final ComponentName TEST_IME_ID2 = Objects.requireNonNull(
+            ComponentName.unflattenFromString("com.android.test.testime2/.InputMethod"));
+
+    /**
+     * {@link ComponentName} for fake {@link InputMethodInfo}.
+     */
+    @NonNull
+    public static final ComponentName TEST_IME_ID3 = Objects.requireNonNull(
+            ComponentName.unflattenFromString("com.android.test.testime3/.InputMethod"));
+
+    /**
+     * Creates a list of fake {@link InputMethodSubtype} for unit testing for the given number.
+     *
+     * @param count The number of fake {@link InputMethodSubtype} objects
+     * @return The list of fake {@link InputMethodSubtype} objects
+     */
+    @NonNull
+    public static ArrayList<InputMethodSubtype> createFakeSubtypes(int count) {
+        final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(count);
+        for (int i = 0; i < count; ++i) {
+            subtypes.add(
+                    new InputMethodSubtype.InputMethodSubtypeBuilder()
+                            .setSubtypeId(i + 0x100)
+                            .setLanguageTag("en-US")
+                            .setSubtypeNameOverride("TestSubtype" + i)
+                            .build());
+        }
+        return subtypes;
+    }
+
+    /**
+     * Creates a fake {@link InputMethodInfo} for unit testing.
+     *
+     * @param componentName {@link ComponentName} of the fake {@link InputMethodInfo}
+     * @param subtypes A list of (fake) {@link InputMethodSubtype}
+     * @return a fake {@link InputMethodInfo} object
+     */
+    @NonNull
+    public static InputMethodInfo createFakeInputMethodInfo(
+            @NonNull ComponentName componentName, @NonNull ArrayList<InputMethodSubtype> subtypes) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = componentName.getPackageName();
+        ai.enabled = true;
+
+        final ServiceInfo si = new ServiceInfo();
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = componentName.getPackageName();
+        si.name = componentName.getClassName();
+        si.exported = true;
+        si.nonLocalizedLabel = "Fake Label";
+
+        final ResolveInfo ri = new ResolveInfo();
+        ri.serviceInfo = si;
+
+        return new InputMethodInfo(ri, false /* isAuxIme */, null /* settingsActivity */,
+                subtypes, 0 /* isDefaultResId */, false /* forceDefault */);
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
index d29bf1a..3635e9a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.BatteryManager;
@@ -49,9 +50,9 @@
     private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100;
 
     private MockClock mMockClock;
+    private BatteryStatsImpl.BatteryStatsConfig mConfig;
     private MockBatteryStatsImpl mBatteryStatsImpl;
 
-
     /**
      * Battery status. Must be one of the following:
      * {@link BatteryManager#BATTERY_STATUS_UNKNOWN}
@@ -91,8 +92,9 @@
 
     @Before
     public void setUp() throws IOException {
+        mConfig = mock(BatteryStatsImpl.BatteryStatsConfig.class);
         mMockClock = new MockClock();
-        mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock,
+        mBatteryStatsImpl = new MockBatteryStatsImpl(mConfig, mMockClock,
                 Files.createTempDirectory("BatteryStatsResetTest").toFile());
         mBatteryStatsImpl.onSystemReady(mock(Context.class));
 
@@ -110,10 +112,7 @@
 
     @Test
     public void testResetOnUnplug_highBatteryLevel() {
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugHighBatteryLevel(true)
-                        .build());
+        when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(true);
 
         long expectedResetTimeUs = 0;
 
@@ -137,10 +136,7 @@
         assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
 
         // disable high battery level reset on unplug.
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugHighBatteryLevel(false)
-                        .build());
+        when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
 
         dischargeToLevel(60);
 
@@ -153,10 +149,7 @@
 
     @Test
     public void testResetOnUnplug_significantCharge() {
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugAfterSignificantCharge(true)
-                        .build());
+        when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(true);
         long expectedResetTimeUs = 0;
 
         unplugBattery();
@@ -186,10 +179,7 @@
         assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
 
         // disable reset on unplug after significant charge.
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugAfterSignificantCharge(false)
-                        .build());
+        when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
 
         // Battery level dropped below 20%.
         dischargeToLevel(15);
@@ -256,11 +246,9 @@
     @Test
     public void testResetWhilePluggedIn_longPlugIn() {
         // disable high battery level reset on unplug.
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugHighBatteryLevel(false)
-                        .setResetOnUnplugAfterSignificantCharge(false)
-                        .build());
+        when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
+        when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
+
         long expectedResetTimeUs = 0;
 
         plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 2d7cb22..6edfede 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -98,10 +98,12 @@
         mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
         mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
         mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
-                        10000)
-                .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        10000);
+                .setPowerStatsThrottlePeriodMillis(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_CPU), 10000)
+                .setPowerStatsThrottlePeriodMillis(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000);
     }
 
     private void initBatteryStats() {
@@ -290,7 +292,8 @@
 
     public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent,
             long throttleMs) {
-        mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs);
+        mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(
+                BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs);
         return this;
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index b5bdafe..d1105a4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -129,6 +129,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return 0;
+        }
+
+        @Override
         public int getDefaultCpuPowerBrackets() {
             return mDefaultCpuPowerBrackets;
         }
@@ -418,8 +423,8 @@
     private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
             int defaultCpuPowerBracketsPerEnergyConsumer) {
         CpuPowerStatsCollector collector = new CpuPowerStatsCollector(
-                new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer),
-                0);
+                new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer)
+        );
         collector.addConsumer(stats -> mCollectedStats = stats);
         collector.setEnabled(true);
         return collector;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
index ca58db1..0275319 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -123,6 +123,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return 0;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mPackageManager;
         }
@@ -348,7 +353,7 @@
     }
 
     private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable {
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
index d80ffb4..29ef3b6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -114,6 +114,11 @@
                 }
 
                 @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
                 public PackageManager getPackageManager() {
                     return mPackageManager;
                 }
@@ -186,7 +191,7 @@
         aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
         aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
 
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty ModemActivityInfo.
@@ -425,7 +430,7 @@
         aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
         aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
 
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 1d48975..2c03f9d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -72,6 +72,11 @@
         this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver());
     }
 
+    MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory) {
+        this(config, clock, historyDirectory, new Handler(Looper.getMainLooper()),
+                new PowerStatsUidResolver());
+    }
+
     MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
             Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
         super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
@@ -137,13 +142,6 @@
         return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
     }
 
-    public MockBatteryStatsImpl setBatteryStatsConfig(BatteryStatsConfig config) {
-        synchronized (this) {
-            mBatteryStatsConfig = config;
-        }
-        return this;
-    }
-
     public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
         mNetworkStats = networkStats;
         return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
index dadcf3f..69d655b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -98,6 +98,11 @@
                 }
 
                 @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
                 public PackageManager getPackageManager() {
                     return mPackageManager;
                 }
@@ -175,7 +180,7 @@
         aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
 
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index b4c012b..a280cfe 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -138,6 +138,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return 0;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mPackageManager;
         }
@@ -317,7 +322,7 @@
     private PowerStats collectPowerStats(boolean hasPowerReporting) {
         when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
index 257a1a6..3ceaf35 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -142,6 +142,11 @@
                 }
 
                 @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
                 public PackageManager getPackageManager() {
                     return mPackageManager;
                 }
@@ -195,7 +200,7 @@
 
         PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty WifiActivityEnergyInfo.
@@ -307,7 +312,7 @@
 
         PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty WifiActivityEnergyInfo.
@@ -420,7 +425,7 @@
 
         PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Establish a baseline
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index b5f0a52..b50684b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -26,6 +26,8 @@
 import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
+import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -1780,9 +1782,17 @@
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
 
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
         mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1791,6 +1801,10 @@
         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
         mTestLooper.dispatchAll();
 
+        // Assume there was a retry and the action did not finish earlier.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
     }
 
@@ -1800,9 +1814,18 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
         mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1827,8 +1850,18 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1847,8 +1880,16 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
         HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
                 .setLogicalAddress(ADDR_PLAYBACK_1)
                 .setPhysicalAddress(0x1000)
@@ -1862,6 +1903,10 @@
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
         mTestLooper.dispatchAll();
 
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+        mTestLooper.dispatchAll();
+
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
         mNativeWrapper.clearResultMessages();
         mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
@@ -1874,6 +1919,41 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
     }
 
+    @Test
+    public void onAddressAllocated_sendSourceChangingMessage_noRequestActiveSourceMessage() {
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage setStreamPathFromTv =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2000);
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Even if the device at the end of this path doesn't answer to this message, TV should not
+        // continue the RequestActiveSourceAction.
+        mHdmiControlService.sendCecCommand(setStreamPathFromTv);
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Assume there was a retry and the action did not finish earlier.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+    }
 
     @Test
     public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
@@ -1900,7 +1980,12 @@
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
 
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // TV will send <Active Source> when it selects its internal source.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
         mTestLooper.dispatchAll();
 
         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
@@ -1930,6 +2015,8 @@
     public void handleStandby_fromActiveSource_standby() {
         mPowerManager.setInteractive(true);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
         mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
                 "HdmiCecLocalDeviceTvTest");
         mTestLooper.dispatchAll();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index a3d57c3..70a0038 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -24,6 +24,8 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
@@ -52,6 +54,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest.permission;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -190,6 +193,8 @@
         getContext().addMockSystemService(Vibrator.class, mVibrator);
         getContext().addMockSystemService(PackageManager.class, mPackageManager);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                anyString())).thenReturn(PERMISSION_DENIED);
 
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -2363,6 +2368,72 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+        // Should beep at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 100% volume
+        NotificationRecord r2 = getBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
new file mode 100644
index 0000000..12ab3e1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ActivityRefresher}.
+ *
+ * <p>Build/Install/Run:
+ *  atest WmTests:ActivityRefresherTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class ActivityRefresherTests extends WindowTestsBase {
+    private Handler mMockHandler;
+    private LetterboxConfiguration mLetterboxConfiguration;
+
+    private ActivityRecord mActivity;
+    private ActivityRefresher mActivityRefresher;
+
+    private ActivityRefresher.Evaluator mEvaluatorFalse =
+            (activity, newConfig, lastReportedConfig) -> false;
+
+    private ActivityRefresher.Evaluator mEvaluatorTrue =
+            (activity, newConfig, lastReportedConfig) -> true;
+
+    private final Configuration mNewConfig = new Configuration();
+    private final Configuration mOldConfig = new Configuration();
+
+    @Before
+    public void setUp() throws Exception {
+        mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+        spyOn(mLetterboxConfiguration);
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(true);
+
+        mMockHandler = mock(Handler.class);
+        when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+                invocation -> {
+                    ((Runnable) invocation.getArgument(0)).run();
+                    return null;
+                });
+
+        mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshDisabled() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(false);
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
+        configureActivityAndDisplay();
+        when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+                .thenReturn(false);
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception {
+        configureActivityAndDisplay();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorFalse);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorFalse);
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ true);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled()
+            throws Exception {
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(false);
+        configureActivityAndDisplay();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp()
+            throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    @Test
+    public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
+
+        mActivityRefresher.onActivityRefreshed(mActivity);
+
+        assertActivityRefreshRequested(false);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+        assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested,
+            boolean cycleThroughStop) throws Exception {
+        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+                .setIsRefreshRequested(true);
+
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
+                cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+
+        verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+                .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+                        refreshCallbackItem, resumeActivityItem);
+    }
+
+    private void configureActivityAndDisplay() {
+        mActivity = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setDisplay(mDisplayContent)
+                // Set the component to be that of the test class in order to enable compat changes
+                .setComponent(ComponentName.createRelative(mContext,
+                        ActivityRefresherTests.class.getName()))
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build()
+                .getTopMostActivity();
+
+        spyOn(mActivity.mLetterboxUiController);
+        doReturn(true).when(
+                mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+
+        doReturn(true).when(mActivity).inFreeformWindowingMode();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 262ba8b..c76acd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -91,6 +91,7 @@
     private CameraManager mMockCameraManager;
     private Handler mMockHandler;
     private LetterboxConfiguration mLetterboxConfiguration;
+    private ActivityRefresher mActivityRefresher;
 
     private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
@@ -132,8 +133,9 @@
                 });
         CameraStateMonitor cameraStateMonitor =
                 new CameraStateMonitor(mDisplayContent, mMockHandler);
-        mDisplayRotationCompatPolicy =
-                new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor);
+        mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+        mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent,
+                cameraStateMonitor, mActivityRefresher);
 
         // Do not show the real toast.
         spyOn(mDisplayRotationCompatPolicy);
@@ -606,7 +608,7 @@
     private void assertActivityRefreshRequested(boolean refreshRequested,
                 boolean cycleThroughStop) throws Exception {
         verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
-                .setIsRefreshAfterRotationRequested(true);
+                .setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
                 cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -628,7 +630,7 @@
 
     private void callOnActivityConfigurationChanging(
             ActivityRecord activity, boolean isDisplayRotationChanging) {
-        mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+        mActivityRefresher.onActivityConfigurationChanging(activity,
                 /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
                 /* newConfig */ createConfigurationWithDisplayRotation(
                         isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0e1a1af..c69faed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -353,6 +353,17 @@
     }
 
     @Test
+    public void testControlTargetChangedWhileProviderHasNoWindow() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final InsetsSourceProvider provider = getController().getOrCreateSourceProvider(
+                ID_STATUS_BAR, statusBars());
+        getController().onBarControlTargetChanged(app, null, null, null);
+        assertNull(getController().getControlsForDispatch(app));
+        provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null);
+        assertNotNull(getController().getControlsForDispatch(app));
+    }
+
+    @Test
     public void testTransientVisibilityOfFixedRotationState() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 1195c93..6b17de4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -1594,7 +1594,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY)
+    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
     public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
         spyOn(mController);
         doReturn(true).when(mController).isVerticalThinLetterboxed();
@@ -1609,7 +1609,7 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY)
+    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
     public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
         spyOn(mController);
         doReturn(true).when(mController).isVerticalThinLetterboxed();
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
index 6f8f008..955b43a 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
@@ -19,7 +19,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="com.android.server.wm.flicker">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="35"/>
     <!-- Read and write traces from external storage -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -46,6 +46,8 @@
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <!-- Allow the test to connect to perfetto trace processor -->
     <uses-permission android:name="android.permission.INTERNET"/>
+    <!-- Allow to query for the Launcher TestInfo on SDK 30+ -->
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
     <application android:requestLegacyExternalStorage="true"
                  android:networkSecurityConfig="@xml/network_security_config"
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index cf4edd5..67825d2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -43,6 +43,7 @@
  *
  * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
  */
+@FlakyTest(bugId = 341209752)
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -168,7 +169,6 @@
         }
     }
 
-    @FlakyTest(bugId = 290736037)
     /** Main activity should go from fullscreen to being a split with secondary activity. */
     @Test
     fun mainActivityLayerGoesFromFullscreenToSplit() {
@@ -203,7 +203,6 @@
         }
     }
 
-    @FlakyTest(bugId = 288591571)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index bc3696b..eed9225 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -205,7 +205,8 @@
                         it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
                     val secondaryVisibleRegion =
                         it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                    overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region)
+                    // TODO(b/340992001): replace coverAtLeast with coverExactly
+                    overlayVisibleRegion.coversAtLeast(secondaryVisibleRegion.region)
                 }
                 .then()
                 .isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index fb92583..379b45c 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -60,14 +60,16 @@
             testApp.launchViaIntent(wmHelper)
             testApp.launchSecondaryActivity(wmHelper)
             secondaryApp.launchViaIntent(wmHelper)
-            tapl.goHome()
-            wmHelper
-                .StateSyncBuilder()
-                .withAppTransitionIdle()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
             startDisplayBounds =
                 wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+
+            // Record the displayBounds before `goHome()` in case the launcher is fixed-portrait.
+            tapl.goHome()
+            wmHelper
+                    .StateSyncBuilder()
+                    .withAppTransitionIdle()
+                    .withHomeActivityVisible()
+                    .waitForAndVerify()
         }
         transitions {
             SplitScreenUtils.enterSplit(
@@ -138,10 +140,6 @@
             check { "ActivityEmbeddingSplitHeight" }
                 .that(leftAELayerRegion.region.bounds.height())
                 .isEqual(rightAELayerRegion.region.bounds.height())
-            check { "SystemSplitHeight" }
-                .that(rightAELayerRegion.region.bounds.height())
-                .isEqual(secondaryAppLayerRegion.region.bounds.height())
-            // TODO(b/292283182): Remove this special case handling.
             check { "ActivityEmbeddingSplitWidth" }
                 .that(
                     abs(
@@ -150,14 +148,6 @@
                     )
                 )
                 .isLower(2)
-            check { "SystemSplitWidth" }
-                .that(
-                    abs(
-                        secondaryAppLayerRegion.region.bounds.width() -
-                            2 * rightAELayerRegion.region.bounds.width()
-                    )
-                )
-                .isLower(2)
         }
     }
 
@@ -170,15 +160,9 @@
                 visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
             val rightAEWindowRegion =
                 visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-            // There's no window for the divider bar.
-            val secondaryAppLayerRegion =
-                visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
             check { "ActivityEmbeddingSplitHeight" }
                 .that(leftAEWindowRegion.region.bounds.height())
                 .isEqual(rightAEWindowRegion.region.bounds.height())
-            check { "SystemSplitHeight" }
-                .that(rightAEWindowRegion.region.bounds.height())
-                .isEqual(secondaryAppLayerRegion.region.bounds.height())
             check { "ActivityEmbeddingSplitWidth" }
                 .that(
                     abs(
@@ -187,14 +171,6 @@
                     )
                 )
                 .isLower(2)
-            check { "SystemSplitWidth" }
-                .that(
-                    abs(
-                        secondaryAppLayerRegion.region.bounds.width() -
-                            2 * rightAEWindowRegion.region.bounds.width()
-                    )
-                )
-                .isLower(2)
         }
     }
 
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 86c21906..917aec1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -14,66 +14,71 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<LinearLayout
+<ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
     android:background="@android:color/holo_orange_light">
 
-    <Button
-        android:id="@+id/launch_secondary_activity_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchSecondaryActivity"
-        android:tag="LEFT_TO_RIGHT"
-        android:text="Launch Secondary Activity" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-    <Button
-        android:id="@+id/launch_secondary_activity_rtl_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchSecondaryActivity"
-        android:tag="RIGHT_TO_LEFT"
-        android:text="Launch Secondary Activity in RTL" />
+        <Button
+            android:id="@+id/launch_secondary_activity_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchSecondaryActivity"
+            android:tag="LEFT_TO_RIGHT"
+            android:text="Launch Secondary Activity" />
 
-    <Button
-        android:id="@+id/launch_secondary_activity_horizontally_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchSecondaryActivity"
-        android:tag="BOTTOM_TO_TOP"
-        android:text="Launch Secondary Activity Horizontally" />
+        <Button
+            android:id="@+id/launch_secondary_activity_rtl_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchSecondaryActivity"
+            android:tag="RIGHT_TO_LEFT"
+            android:text="Launch Secondary Activity in RTL" />
 
-    <Button
-        android:id="@+id/launch_placeholder_split_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchPlaceholderSplit"
-        android:tag="LEFT_TO_RIGHT"
-        android:text="Launch Placeholder Split" />
+        <Button
+            android:id="@+id/launch_secondary_activity_horizontally_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchSecondaryActivity"
+            android:tag="BOTTOM_TO_TOP"
+            android:text="Launch Secondary Activity Horizontally" />
 
-    <Button
-        android:id="@+id/launch_always_expand_activity_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchAlwaysExpandActivity"
-        android:text="Launch Always Expand Activity" />
+        <Button
+            android:id="@+id/launch_placeholder_split_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchPlaceholderSplit"
+            android:tag="LEFT_TO_RIGHT"
+            android:text="Launch Placeholder Split" />
 
-    <Button
-        android:id="@+id/launch_placeholder_split_rtl_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchPlaceholderSplit"
-        android:tag="RIGHT_TO_LEFT"
-        android:text="Launch Placeholder Split in RTL" />
+        <Button
+            android:id="@+id/launch_always_expand_activity_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchAlwaysExpandActivity"
+            android:text="Launch Always Expand Activity" />
 
-    <Button
-        android:id="@+id/launch_trampoline_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchTrampolineActivity"
-        android:tag="LEFT_TO_RIGHT"
-        android:text="Launch Trampoline Activity" />
+        <Button
+            android:id="@+id/launch_placeholder_split_rtl_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchPlaceholderSplit"
+            android:tag="RIGHT_TO_LEFT"
+            android:text="Launch Placeholder Split in RTL" />
 
-</LinearLayout>
+        <Button
+            android:id="@+id/launch_trampoline_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchTrampolineActivity"
+            android:tag="LEFT_TO_RIGHT"
+            android:text="Launch Trampoline Activity" />
+
+    </LinearLayout>
+</ScrollView>
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
index 3a2a3be..ae32bda 100644
--- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -16,6 +16,8 @@
 
 package android.hardware.input
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.content.ContextWrapper
 import android.graphics.drawable.Drawable
 import android.platform.test.annotations.Presubmit
@@ -54,16 +56,16 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
     fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
-        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         val drawable = createDrawable()!!
         assertEquals(WIDTH, drawable.intrinsicWidth)
         assertEquals(HEIGHT, drawable.intrinsicHeight)
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
     fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
-        setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         assertNull(createDrawable())
     }
 }
\ No newline at end of file
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index e2b0c36..bcd56ad 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -21,6 +21,7 @@
 import android.os.Handler
 import android.os.HandlerExecutor
 import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.Presubmit
 import android.platform.test.flag.junit.SetFlagsRule
 import android.view.KeyEvent
@@ -50,6 +51,10 @@
  */
 @Presubmit
 @RunWith(MockitoJUnitRunner::class)
+@EnableFlags(
+    com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+    com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL,
+)
 class StickyModifierStateListenerTest {
 
     @get:Rule
@@ -67,10 +72,6 @@
 
     @Before
     fun setUp() {
-        // Enable Sticky keys feature
-        rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
-        rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL)
-
         context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
         inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
         inputManager = InputManager(context)
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
similarity index 99%
rename from tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 001a09a..be9fb1b 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -34,7 +34,7 @@
 import perfetto.protos.ProtologCommon;
 import perfetto.protos.ProtologConfig;
 
-public class PerfettoDataSourceTest {
+public class ProtologDataSourceTest {
     @Before
     public void before() {
         assumeTrue(android.tracing.Flags.perfettoProtologTracing());
diff --git a/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
new file mode 100644
index 0000000..d6f3148
--- /dev/null
+++ b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.hardware.usb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.flags.Flags;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.StringReader;
+
+/**
+ * Unit tests for {@link android.hardware.usb.DeviceFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeviceFilterTest {
+
+    private static final int VID = 10;
+    private static final int PID = 11;
+    private static final int CLASS = 12;
+    private static final int SUBCLASS = 13;
+    private static final int PROTOCOL = 14;
+    private static final String MANUFACTURER = "Google";
+    private static final String PRODUCT = "Test";
+    private static final String SERIAL_NO = "4AL23";
+    private static final String INTERFACE_NAME = "MTP";
+
+    private MockitoSession mStaticMockSession;
+
+    @Before
+    public void setUp() throws Exception {
+        mStaticMockSession = ExtendedMockito.mockitoSession()
+                .mockStatic(Flags.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mStaticMockSession.finishMocking();
+    }
+
+    @Test
+    public void testConstructorFromValues_interfaceNameIsInitialized() {
+        DeviceFilter deviceFilter = new DeviceFilter(
+                VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+                PRODUCT, SERIAL_NO, INTERFACE_NAME
+        );
+
+        verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+    }
+
+    @Test
+    public void testConstructorFromUsbDevice_interfaceNameIsNull() {
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getVendorId()).thenReturn(VID);
+        when(usbDevice.getProductId()).thenReturn(PID);
+        when(usbDevice.getDeviceClass()).thenReturn(CLASS);
+        when(usbDevice.getDeviceSubclass()).thenReturn(SUBCLASS);
+        when(usbDevice.getDeviceProtocol()).thenReturn(PROTOCOL);
+        when(usbDevice.getManufacturerName()).thenReturn(MANUFACTURER);
+        when(usbDevice.getProductName()).thenReturn(PRODUCT);
+        when(usbDevice.getSerialNumber()).thenReturn(SERIAL_NO);
+
+        DeviceFilter deviceFilter = new DeviceFilter(usbDevice);
+
+        verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+    }
+
+    @Test
+    public void testConstructorFromDeviceFilter_interfaceNameIsInitialized() {
+        DeviceFilter originalDeviceFilter = new DeviceFilter(
+                VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+                PRODUCT, SERIAL_NO, INTERFACE_NAME
+        );
+
+        DeviceFilter deviceFilter = new DeviceFilter(originalDeviceFilter);
+
+        verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+    }
+
+
+    @Test
+    public void testReadFromXml_interfaceNamePresent_propertyIsInitialized() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+
+        assertThat(deviceFilter.mInterfaceName).isEqualTo("MTP");
+    }
+
+    @Test
+    public void testReadFromXml_interfaceNameAbsent_propertyIsNull() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+    }
+
+    @Test
+    public void testWrite_withInterfaceName() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+        XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+        deviceFilter.write(serializer);
+
+        verify(serializer).attribute(null, "interface-name", "MTP");
+    }
+
+    @Test
+    public void testWrite_withoutInterfaceName() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+        XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+        deviceFilter.write(serializer);
+
+        verify(serializer, times(0)).attribute(eq(null), eq("interface-name"), any());
+    }
+
+    @Test
+    public void testToString() {
+        DeviceFilter deviceFilter = new DeviceFilter(
+                VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+                PRODUCT, SERIAL_NO, INTERFACE_NAME
+        );
+
+        assertThat(deviceFilter.toString()).isEqualTo(
+                "DeviceFilter[mVendorId=10,mProductId=11,mClass=12,mSubclass=13,mProtocol=14,"
+                + "mManufacturerName=Google,mProductName=Test,mSerialNumber=4AL23,"
+                + "mInterfaceName=MTP]");
+    }
+
+    @Test
+    public void testMatch_interfaceNameMatches_returnTrue() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml(
+                "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+                + "interface-name=\"MTP\"/>");
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getInterfaceCount()).thenReturn(1);
+        when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+            /* id= */ 0,
+            /* alternateSetting= */ 0,
+            /* name= */ "MTP",
+            /* class= */ 255,
+            /* subClass= */ 255,
+            /* protocol= */ 0));
+
+        assertTrue(deviceFilter.matches(usbDevice));
+    }
+
+    @Test
+    public void testMatch_interfaceNameMismatch_returnFalse() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml(
+                "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+                + "interface-name=\"MTP\"/>");
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getInterfaceCount()).thenReturn(1);
+        when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+            /* id= */ 0,
+            /* alternateSetting= */ 0,
+            /* name= */ "UVC",
+            /* class= */ 255,
+            /* subClass= */ 255,
+            /* protocol= */ 0));
+
+        assertFalse(deviceFilter.matches(usbDevice));
+    }
+
+    @Test
+    public void testMatch_interfaceNameMismatchFlagDisabled_returnTrue() throws Exception {
+        when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(false);
+        DeviceFilter deviceFilter = getDeviceFilterFromXml(
+                "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+                + "interface-name=\"MTP\"/>");
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getInterfaceCount()).thenReturn(1);
+        when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+            /* id= */ 0,
+            /* alternateSetting= */ 0,
+            /* name= */ "UVC",
+            /* class= */ 255,
+            /* subClass= */ 255,
+            /* protocol= */ 0));
+
+        assertTrue(deviceFilter.matches(usbDevice));
+    }
+
+    private void verifyDeviceFilterConfigurationExceptInterfaceName(DeviceFilter deviceFilter) {
+        assertThat(deviceFilter.mVendorId).isEqualTo(VID);
+        assertThat(deviceFilter.mProductId).isEqualTo(PID);
+        assertThat(deviceFilter.mClass).isEqualTo(CLASS);
+        assertThat(deviceFilter.mSubclass).isEqualTo(SUBCLASS);
+        assertThat(deviceFilter.mProtocol).isEqualTo(PROTOCOL);
+        assertThat(deviceFilter.mManufacturerName).isEqualTo(MANUFACTURER);
+        assertThat(deviceFilter.mProductName).isEqualTo(PRODUCT);
+        assertThat(deviceFilter.mSerialNumber).isEqualTo(SERIAL_NO);
+    }
+
+    private DeviceFilter getDeviceFilterFromXml(String xml) throws Exception {
+        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+        XmlPullParser parser = factory.newPullParser();
+        parser.setInput(new StringReader(xml));
+        XmlUtils.nextElement(parser);
+
+        return DeviceFilter.read(parser);
+    }
+
+}