Merge "Rename STLState.snapToScene() to snapTo()" into main
diff --git a/cmds/am/am.sh b/cmds/am/am.sh
index 76ec214..f099be3 100755
--- a/cmds/am/am.sh
+++ b/cmds/am/am.sh
@@ -1,11 +1,10 @@
 #!/system/bin/sh
 
-# set to top-app process group
-settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
-
 if [ "$1" != "instrument" ] ; then
     cmd activity "$@"
 else
+    # set to top-app process group for instrument
+    settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
     base=/system
     export CLASSPATH=$base/framework/am.jar
     exec app_process $base/bin com.android.commands.am.Am "$@"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6310d32..696bc82 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupProgress;
@@ -73,6 +74,8 @@
             "Error: Could not access the backup transport.  Is the system running?";
     private static final String PM_NOT_RUNNING_ERR =
             "Error: Could not access the Package Manager.  Is the system running?";
+    private static final String INVALID_USER_ID_ERR_TEMPLATE =
+            "Error: Invalid user id (%d).\n";
 
     private String[] mArgs;
     private int mNextArg;
@@ -104,6 +107,11 @@
         mArgs = args;
         mNextArg = 0;
         int userId = parseUserId();
+        if (userId < 0) {
+            System.err.printf(INVALID_USER_ID_ERR_TEMPLATE, userId);
+            return;
+        }
+
         String op = nextArg();
         Slog.v(TAG, "Running " + op + " for user:" + userId);
 
@@ -955,12 +963,15 @@
 
     private int parseUserId() {
         String arg = nextArg();
-        if ("--user".equals(arg)) {
-            return UserHandle.parseUserArg(nextArg());
-        } else {
+        if (!"--user".equals(arg)) {
             mNextArg--;
             return UserHandle.USER_SYSTEM;
         }
+        int userId = UserHandle.parseUserArg(nextArg());
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+        return userId;
     }
 
     private static void showUsage() {
diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp
index e728bd2..516de33 100644
--- a/cmds/uinput/tests/Android.bp
+++ b/cmds/uinput/tests/Android.bp
@@ -18,3 +18,17 @@
         "device-tests",
     ],
 }
+
+android_ravenwood_test {
+    name: "UinputTestsRavenwood",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "frameworks-base-testutils",
+        "platform-test-annotations",
+        "truth",
+        "uinput",
+    ],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 050cad4..d4ed533 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2419,6 +2419,7 @@
     field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
     field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
     field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
+    field @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final int accessibilityActionSetExtendedSelection;
     field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
     field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
     field public static final int accessibilityActionShowTextSuggestions = 16908376; // 0x1020058
@@ -56266,6 +56267,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo getParent();
     method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
     method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
+    method @FlaggedApi("android.view.accessibility.a11y_selection_api") @Nullable public android.view.accessibility.AccessibilityNodeInfo.Selection getSelection();
     method @Nullable public CharSequence getStateDescription();
     method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
     method public CharSequence getText();
@@ -56374,6 +56376,7 @@
     method public void setScreenReaderFocusable(boolean);
     method public void setScrollable(boolean);
     method public void setSelected(boolean);
+    method @FlaggedApi("android.view.accessibility.a11y_selection_api") public void setSelection(@Nullable android.view.accessibility.AccessibilityNodeInfo.Selection);
     method public void setShowingHintText(boolean);
     method public void setSource(android.view.View);
     method public void setSource(android.view.View, int);
@@ -56406,6 +56409,7 @@
     field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
     field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
     field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+    field @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final String ACTION_ARGUMENT_SELECTION_PARCELABLE = "android.view.accessibility.action.ARGUMENT_SELECTION_PARCELABLE";
     field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
     field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
     field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
@@ -56503,6 +56507,7 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SCROLL_TO_POSITION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SCROLL_UP;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SELECT;
+    field @FlaggedApi("android.view.accessibility.a11y_selection_api") @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_EXTENDED_SELECTION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_PROGRESS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
@@ -56588,6 +56593,26 @@
     field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
   }
 
+  @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final class AccessibilityNodeInfo.Selection implements android.os.Parcelable {
+    ctor public AccessibilityNodeInfo.Selection(@NonNull android.view.accessibility.AccessibilityNodeInfo.SelectionPosition, @NonNull android.view.accessibility.AccessibilityNodeInfo.SelectionPosition);
+    method public int describeContents();
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.SelectionPosition getEnd();
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.SelectionPosition getStart();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.Selection> CREATOR;
+  }
+
+  @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final class AccessibilityNodeInfo.SelectionPosition implements android.os.Parcelable {
+    ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.accessibility.AccessibilityNodeInfo, int);
+    ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.View, int);
+    ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.View, int, int);
+    method public int describeContents();
+    method @Nullable public android.view.accessibility.AccessibilityNodeInfo getNode();
+    method public int getOffset();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.SelectionPosition> CREATOR;
+  }
+
   public static final class AccessibilityNodeInfo.TouchDelegateInfo implements android.os.Parcelable {
     ctor public AccessibilityNodeInfo.TouchDelegateInfo(@NonNull java.util.Map<android.graphics.Region,android.view.View>);
     method public int describeContents();
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index e71dffa..577113b 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,3 +1,4 @@
+
 // Baseline format: 1.0
 BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
     Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
@@ -243,8 +244,6 @@
     Field 'ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED' is missing @BroadcastBehavior
 BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
     Field 'ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE' is missing @BroadcastBehavior
-
-
 DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
     Method android.accounts.AccountManager.newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
 DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
@@ -381,8 +380,6 @@
     Method android.webkit.WebViewDatabase.hasFormData(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
 DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
     Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
-
-
 FlaggedApiLiteral: android.Manifest.permission#BIND_APP_FUNCTION_SERVICE:
     @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER).
 FlaggedApiLiteral: android.Manifest.permission#BIND_TV_AD_SERVICE:
@@ -405,26 +402,22 @@
     @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_SDK_LIB_INDEPENDENCE).
 FlaggedApiLiteral: android.R.attr#supplementalDescription:
     @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION).
+FlaggedApiLiteral: android.R.id#accessibilityActionSetExtendedSelection:
+    @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_SELECTION_API).
 FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
     @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
 FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
     @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
 FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_SUCCESS:
     @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
-
-
 InvalidNullabilityOverride: android.app.Notification.TvExtender#extend(android.app.Notification.Builder) parameter #0:
     Invalid nullability on parameter `builder` in method `extend`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
 InvalidNullabilityOverride: android.media.midi.MidiUmpDeviceService#onBind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-
-
 KotlinOperator: android.graphics.Matrix44#get(int, int):
     Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
 KotlinOperator: android.graphics.Matrix44#set(int, int, float):
     Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
 MissingGetterMatchingBuilder: android.os.RemoteCallbackList.Builder#setInterfaceDiedCallback(android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>):
     android.os.RemoteCallbackList does not declare a `getInterfaceDiedCallback()` method matching method android.os.RemoteCallbackList.Builder.setInterfaceDiedCallback(android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>)
 RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
@@ -1117,14 +1110,10 @@
     Method 'setBlockNetworkLoads' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
     Method 'setGeolocationEnabled' documentation mentions permissions without declaring @RequiresPermission
-
-
 Todo: android.hardware.camera2.params.StreamConfigurationMap:
     Documentation mentions 'TODO'
 Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
     Documentation mentions 'TODO'
-
-
 UnflaggedApi: android.R.color#on_surface_disabled_material:
     New API must be flagged with @FlaggedApi: field android.R.color.on_surface_disabled_material
 UnflaggedApi: android.R.color#outline_disabled_material:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e8ff546..9e9e3c2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1806,17 +1806,17 @@
   }
 
   public class InputSettings {
-    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
-    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
+    method public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
+    method public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
     method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysDelay(@NonNull android.content.Context);
     method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysTimeout(@NonNull android.content.Context);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") public static boolean isAccessibilityMouseKeysEnabled(@NonNull android.content.Context);
-    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
+    method public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
     method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static boolean isRepeatKeysEnabled(@NonNull android.content.Context);
-    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityMouseKeysEnabled(@NonNull android.content.Context, boolean);
-    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
-    method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
     method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysDelay(@NonNull android.content.Context, int);
     method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysEnabled(@NonNull android.content.Context, boolean);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dce15b8..0ca4a32 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11204,10 +11204,15 @@
      * </pre>
      *
      *
-     *
+     * <p>
      * NOTE: The progress bar layout will be mirrored for RTL layout.
+     * </p>
+     *
+     * <p>
      * NOTE: The extras set by {@link Notification.Builder#setProgress} will be overridden by
-     * the values set on this style object when the notification is built.
+     * the values set on this style object when the notification is built.  Therefore, that method
+     * is not used with this style.
+     * </p>
      *
      */
     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
@@ -11370,7 +11375,7 @@
             if (mProgressPoints == null) {
                 mProgressPoints = new ArrayList<>();
             }
-            if (point.getPosition() >= 0) {
+            if (point.getPosition() > 0) {
                 mProgressPoints.add(point);
 
                 if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) {
@@ -11379,7 +11384,7 @@
                 }
 
             } else {
-                Log.w(TAG, "Dropped the point. The position is a negative integer.");
+                Log.w(TAG, "Dropped the point. The position is a negative or zero integer.");
             }
 
             return this;
@@ -11893,7 +11898,9 @@
                 final List<Point> points = new ArrayList<>();
                 for (Point point : mProgressPoints) {
                     final int position = point.getPosition();
-                    if (position < 0 || position > totalLength) continue;
+                    // The points at start/end aren't supposed to show in the progress bar.
+                    // Therefore those are also dropped here.
+                    if (position <= 0 || position >= totalLength) continue;
                     points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
                     if (points.size() == MAX_PROGRESS_POINT_LIMIT) {
                         break;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 566e78a..2b0e941 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -277,6 +277,23 @@
      */
     public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
 
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TRANSPORT_FLAG_" }, value = {
+            TRANSPORT_FLAG_EXTEND_PATCH_DIFF,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransportFlags {}
+
+    /**
+     * A security flag that allows transports to be attached to devices that may be more vulnerable
+     * due to infrequent updates. Can only be used for associations with
+     * {@link AssociationRequest#DEVICE_PROFILE_WEARABLE_SENSING} device profile.
+     *
+     * @hide
+     */
+    public static final int TRANSPORT_FLAG_EXTEND_PATCH_DIFF = 1;
+
     /**
      * Callback for applications to receive updates about and the outcome of
      * {@link AssociationRequest} issued via {@code associate()} call.
@@ -1452,7 +1469,52 @@
             }
 
             try {
-                final Transport transport = new Transport(associationId, in, out);
+                final Transport transport = new Transport(associationId, in, out, 0);
+                mTransports.put(associationId, transport);
+                transport.start();
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to attach transport", e);
+            }
+        }
+    }
+
+    /**
+     * Attach a bidirectional communication stream to be used as a transport channel for
+     * transporting system data between associated devices. Flags can be provided to further
+     * customize the behavior of the transport.
+     *
+     * @param associationId id of the associated device.
+     * @param in Already connected stream of data incoming from remote
+     *           associated device.
+     * @param out Already connected stream of data outgoing to remote associated
+     *            device.
+     * @param flags Flags to customize transport behavior.
+     * @throws DeviceNotAssociatedException Thrown if the associationId was not previously
+     * associated with this app.
+     *
+     * @see #buildPermissionTransferUserConsentIntent(int)
+     * @see #startSystemDataTransfer(int, Executor, OutcomeReceiver)
+     * @see #detachSystemDataTransport(int)
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
+    public void attachSystemDataTransport(int associationId,
+            @NonNull InputStream in,
+            @NonNull OutputStream out,
+            @TransportFlags int flags) throws DeviceNotAssociatedException {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
+        synchronized (mTransports) {
+            if (mTransports.contains(associationId)) {
+                detachSystemDataTransport(associationId);
+            }
+
+            try {
+                final Transport transport = new Transport(associationId, in, out, flags);
                 mTransports.put(associationId, transport);
                 transport.start();
             } catch (IOException e) {
@@ -1931,16 +1993,22 @@
         private final int mAssociationId;
         private final InputStream mRemoteIn;
         private final OutputStream mRemoteOut;
+        private final int mFlags;
 
         private InputStream mLocalIn;
         private OutputStream mLocalOut;
 
         private volatile boolean mStopped;
 
-        public Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+        Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+            this(associationId, remoteIn, remoteOut, 0);
+        }
+
+        Transport(int associationId, InputStream remoteIn, OutputStream remoteOut, int flags) {
             mAssociationId = associationId;
             mRemoteIn = remoteIn;
             mRemoteOut = remoteOut;
+            mFlags = flags;
         }
 
         public void start() throws IOException {
@@ -1957,7 +2025,7 @@
 
             try {
                 mService.attachSystemDataTransport(mContext.getOpPackageName(),
-                        mContext.getUserId(), mAssociationId, remoteFd);
+                        mContext.getUserId(), mAssociationId, remoteFd, mFlags);
             } catch (RemoteException e) {
                 throw new IOException("Failed to configure transport", e);
             }
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index a2b7dd9..787e8b6 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -113,7 +113,7 @@
         in ISystemDataTransferCallback callback);
 
     @EnforcePermission("DELIVER_COMPANION_MESSAGES")
-    void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
+    void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd, int flags);
 
     @EnforcePermission("DELIVER_COMPANION_MESSAGES")
     void detachSystemDataTransport(String packageName, int userId, int associationId);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 37f3f17..e645060 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1643,6 +1643,19 @@
     public static final long OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION = 327313645L;
 
     /**
+     * When the override is enabled, the activity receives configuration coupled with caption bar
+     * insets. Normally, caption bar insets are decoupled from configuration.
+     *
+     * <p>Override applies only if the activity targets SDK level 34 or earlier version.
+     *
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    public static final long OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS = 388014743L;
+
+    /**
      * Optional set of a certificates identifying apps that are allowed to embed this activity. From
      * the "knownActivityEmbeddingCerts" attribute.
      */
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 027eb9d..88fbdad 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -137,4 +137,15 @@
     namespace: "resource_manager"
     description: "flag always meant to be false, for testing resource flagging within cts tests"
     bug: "377974898"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "use_new_aconfig_storage"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Retrieve flag values from new Aconfig flag storage in AconfigFlags.java"
+    bug: "352348353"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index fded882..d891916 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -641,6 +641,9 @@
      * is triggered whenever the properties of a {@link android.view.Display}, such as size,
      * state, density are modified.
      *
+     * This event is not triggered for refresh rate changes as they can change very often.
+     * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
+     *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
      *
      */
@@ -839,6 +842,9 @@
      * Registers a display listener to receive notifications about when
      * displays are added, removed or changed.
      *
+     * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
+     * instead to subscribe for explicit events of interest
+     *
      * @param listener The listener to register.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
@@ -847,7 +853,9 @@
      */
     public void registerDisplayListener(DisplayListener listener, Handler handler) {
         registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
-                | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
+                | EVENT_TYPE_DISPLAY_CHANGED
+                | EVENT_TYPE_DISPLAY_REFRESH_RATE
+                | EVENT_TYPE_DISPLAY_REMOVED);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b5715ed..339dbf2 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,29 +1766,23 @@
         }
 
         if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
-            // For backward compatibility, a client subscribing to
-            // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
-            // RR changes
-            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
-                    | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
         }
 
-        if ((eventFlags
-                & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+        if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
             baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
 
-        if (Flags.displayListenerPerformanceImprovements()) {
-            if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
-                baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
-            }
+        if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+        }
 
+        if (Flags.displayListenerPerformanceImprovements()) {
             if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
                 baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
             }
         }
 
-
         return baseEventMask;
     }
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 0ead823..49db54d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,7 +19,6 @@
 import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
 import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
 import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
-import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
 import static com.android.hardware.input.Flags.keyboardGlyphMap;
 
 import android.Manifest;
@@ -966,9 +965,6 @@
     @Nullable
     public Drawable getKeyboardLayoutPreview(@Nullable KeyboardLayout keyboardLayout, int width,
             int height) {
-        if (!keyboardLayoutPreviewFlag()) {
-            return null;
-        }
         PhysicalKeyLayout keyLayout = new PhysicalKeyLayout(
                 mGlobal.getKeyCharacterMap(keyboardLayout), keyboardLayout);
         return new KeyboardLayoutPreviewDrawable(mContext, keyLayout, width, height);
@@ -1403,9 +1399,6 @@
     @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
     public void registerStickyModifierStateListener(@NonNull Executor executor,
             @NonNull StickyModifierStateListener listener) throws IllegalArgumentException {
-        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
-            return;
-        }
         mGlobal.registerStickyModifierStateListener(executor, listener);
     }
 
@@ -1419,9 +1412,6 @@
     @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
     public void unregisterStickyModifierStateListener(
             @NonNull StickyModifierStateListener listener) {
-        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
-            return;
-        }
         mGlobal.unregisterStickyModifierStateListener(listener);
     }
 
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index af40188..3d4b885 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -16,15 +16,9 @@
 
 package android.hardware.input;
 
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
 import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
-import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
-import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
 import static com.android.hardware.input.Flags.mouseScrollingAcceleration;
 import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
 import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
@@ -871,21 +865,6 @@
     }
 
     /**
-     * Whether Accessibility bounce keys feature is enabled.
-     *
-     * <p>
-     * Bounce keys’ is an accessibility feature to aid users who have physical disabilities,
-     * that allows the user to configure the device to ignore rapid, repeated keypresses of the
-     * same key.
-     * </p>
-     *
-     * @hide
-     */
-    public static boolean isAccessibilityBounceKeysFeatureEnabled() {
-        return keyboardA11yBounceKeysFlag();
-    }
-
-    /**
      * Whether Accessibility bounce keys is enabled.
      *
      * <p>
@@ -912,11 +891,7 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
     public static int getAccessibilityBounceKeysThreshold(@NonNull Context context) {
-        if (!isAccessibilityBounceKeysFeatureEnabled()) {
-            return 0;
-        }
         return Settings.Secure.getIntForUser(context.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, 0, UserHandle.USER_CURRENT);
     }
@@ -936,13 +911,9 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
     @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
     public static void setAccessibilityBounceKeysThreshold(@NonNull Context context,
             int thresholdTimeMillis) {
-        if (!isAccessibilityBounceKeysFeatureEnabled()) {
-            return;
-        }
         if (thresholdTimeMillis < 0
                 || thresholdTimeMillis > MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS) {
             throw new IllegalArgumentException(
@@ -955,21 +926,6 @@
     }
 
     /**
-     * Whether Accessibility slow keys feature flags is enabled.
-     *
-     * <p>
-     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
-     * allows the user to specify the duration for which one must press-and-hold a key before the
-     * system accepts the keypress.
-     * </p>
-     *
-     * @hide
-     */
-    public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
-        return keyboardA11ySlowKeysFlag();
-    }
-
-    /**
      * Whether Accessibility slow keys is enabled.
      *
      * <p>
@@ -996,11 +952,7 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
     public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
-        if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
-            return 0;
-        }
         return Settings.Secure.getIntForUser(context.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_SLOW_KEYS, 0, UserHandle.USER_CURRENT);
     }
@@ -1020,13 +972,9 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
     @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
     public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
             int thresholdTimeMillis) {
-        if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
-            return;
-        }
         if (thresholdTimeMillis < 0
                 || thresholdTimeMillis > MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS) {
             throw new IllegalArgumentException(
@@ -1039,23 +987,6 @@
     }
 
     /**
-     * Whether Accessibility sticky keys feature is enabled.
-     *
-     * <p>
-     * 'Sticky keys' is an accessibility feature that assists users who have physical
-     * disabilities or help users reduce repetitive strain injury. It serializes keystrokes
-     * instead of pressing multiple keys at a time, allowing the user to press and release a
-     * modifier key, such as Shift, Ctrl, Alt, or any other modifier key, and have it remain
-     * active until any other key is pressed.
-     * </p>
-     *
-     * @hide
-     */
-    public static boolean isAccessibilityStickyKeysFeatureEnabled() {
-        return keyboardA11yStickyKeysFlag();
-    }
-
-    /**
      * Whether Accessibility sticky keys is enabled.
      *
      * <p>
@@ -1069,11 +1000,7 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
     public static boolean isAccessibilityStickyKeysEnabled(@NonNull Context context) {
-        if (!isAccessibilityStickyKeysFeatureEnabled()) {
-            return false;
-        }
         return Settings.Secure.getIntForUser(context.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_STICKY_KEYS, 0, UserHandle.USER_CURRENT) != 0;
     }
@@ -1092,13 +1019,9 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
     @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
     public static void setAccessibilityStickyKeysEnabled(@NonNull Context context,
             boolean enabled) {
-        if (!isAccessibilityStickyKeysFeatureEnabled()) {
-            return;
-        }
         Settings.Secure.putIntForUser(context.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_STICKY_KEYS, enabled ? 1 : 0,
                 UserHandle.USER_CURRENT);
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 8d58296..6c2ce36 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -8,35 +8,6 @@
 
 flag {
     namespace: "input_native"
-    name: "keyboard_layout_preview_flag"
-    description: "Controls whether a preview will be shown in Settings when selecting a physical keyboard layout"
-    bug: "293579375"
-}
-
-
-flag {
-    namespace: "input_native"
-    name: "keyboard_a11y_sticky_keys_flag"
-    description: "Controls if the sticky keys accessibility feature for physical keyboard is available to the user"
-    bug: "294546335"
-}
-
-flag {
-    namespace: "input_native"
-    name: "keyboard_a11y_bounce_keys_flag"
-    description: "Controls if the bounce keys accessibility feature for physical keyboard is available to the user"
-    bug: "294546335"
-}
-
-flag {
-    namespace: "input_native"
-    name: "keyboard_a11y_slow_keys_flag"
-    description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
-    bug: "294546335"
-}
-
-flag {
-    namespace: "input_native"
     name: "keyboard_glyph_map"
     description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
     bug: "345440920"
diff --git a/media/java/android/media/Image.java b/core/java/android/media/Image.java
similarity index 100%
rename from media/java/android/media/Image.java
rename to core/java/android/media/Image.java
diff --git a/media/java/android/media/ImageReader.java b/core/java/android/media/ImageReader.java
similarity index 100%
rename from media/java/android/media/ImageReader.java
rename to core/java/android/media/ImageReader.java
diff --git a/media/java/android/media/ImageUtils.java b/core/java/android/media/ImageUtils.java
similarity index 100%
rename from media/java/android/media/ImageUtils.java
rename to core/java/android/media/ImageUtils.java
diff --git a/media/java/android/media/ImageWriter.java b/core/java/android/media/ImageWriter.java
similarity index 100%
rename from media/java/android/media/ImageWriter.java
rename to core/java/android/media/ImageWriter.java
diff --git a/media/java/android/media/PublicFormatUtils.java b/core/java/android/media/PublicFormatUtils.java
similarity index 100%
rename from media/java/android/media/PublicFormatUtils.java
rename to core/java/android/media/PublicFormatUtils.java
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 2d9d025..1a54f4d 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -159,7 +159,7 @@
      */
     public void execute(Message message) {
         checkReleased();
-        if (Looper.myLooper() == mLooper) {
+        if (mLooper.isCurrentThread()) {
             // This is being called from the thread it should be executed on, we can just dispatch.
             message.target.dispatchMessage(message);
         } else {
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 5b527c7..1b65a88 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1124,12 +1124,13 @@
         }
 
         final Uri documentUri = extraUri;
-        final String authority = documentUri.getAuthority();
+        final String authorityWithoutUserId = getAuthorityWithoutUserId(documentUri.getAuthority());
         final String documentId = DocumentsContract.getDocumentId(documentUri);
 
-        if (!mAuthority.equals(authority)) {
+        if (!mAuthority.equals(authorityWithoutUserId)) {
             throw new SecurityException(
-                    "Requested authority " + authority + " doesn't match provider " + mAuthority);
+                    "Requested authority " + authorityWithoutUserId + " doesn't match provider "
+                            + mAuthority);
         }
 
         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3cd7a00..f1a9514 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13006,6 +13006,24 @@
         public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled";
 
         /**
+         * Toggle for whether to redact OTP notification while connected to wifi. Defaults to
+         * false/0.
+         * @hide
+         */
+        @Readable
+        public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI =
+                "redact_otp_on_wifi";
+
+        /**
+         * Toggle for whether to immediately redact OTP notifications, or require the device to be
+         * locked for 10 minutes. Defaults to false/0
+         * @hide
+         */
+        @Readable
+        public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY =
+                "remove_otp_redaction_delay";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 58b2a67..a4fc342 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -259,19 +259,20 @@
         int FRAME_DEADLINE = 9;
         int FRAME_START_TIME = 10;
         int FRAME_INTERVAL = 11;
-        int SYNC_QUEUED = 12;
-        int SYNC_START = 13;
-        int ISSUE_DRAW_COMMANDS_START = 14;
-        int SWAP_BUFFERS = 15;
-        int FRAME_COMPLETED = 16;
-        int DEQUEUE_BUFFER_DURATION = 17;
-        int QUEUE_BUFFER_DURATION = 18;
-        int GPU_COMPLETED = 19;
-        int SWAP_BUFFERS_COMPLETED = 20;
-        int DISPLAY_PRESENT_TIME = 21;
-        int COMMAND_SUBMISSION_COMPLETED = 22;
+        int WORKLOAD_TARGET = 12;
+        int SYNC_QUEUED = 13;
+        int SYNC_START = 14;
+        int ISSUE_DRAW_COMMANDS_START = 15;
+        int SWAP_BUFFERS = 16;
+        int FRAME_COMPLETED = 17;
+        int DEQUEUE_BUFFER_DURATION = 18;
+        int QUEUE_BUFFER_DURATION = 19;
+        int GPU_COMPLETED = 20;
+        int SWAP_BUFFERS_COMPLETED = 21;
+        int DISPLAY_PRESENT_TIME = 22;
+        int COMMAND_SUBMISSION_COMPLETED = 23;
 
-        int FRAME_STATS_COUNT = 23; // must always be last and in sync with
+        int FRAME_STATS_COUNT = 24; // must always be last and in sync with
                                     // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
     }
 
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 195896d..0e78bfd 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -180,7 +180,7 @@
             final MotionEvent motionEvent = (MotionEvent)event;
             if (motionEvent.isTouchEvent()) {
                 onTouchEvent(motionEvent, nestingLevel);
-            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+            } else if (motionEvent.isFromSource(InputDevice.SOURCE_TRACKBALL)) {
                 onTrackballEvent(motionEvent, nestingLevel);
             } else {
                 onGenericMotionEvent(motionEvent, nestingLevel);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0d6f827..80b4f2c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -272,9 +272,9 @@
 import android.window.OnBackInvokedDispatcher;
 import android.window.ScreenCapture;
 import android.window.SurfaceSyncGroup;
-import android.window.WindowContext;
 import android.window.WindowOnBackInvokedDispatcher;
 import android.window.WindowTokenClient;
+import android.window.WindowTokenClientController;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -6614,12 +6614,15 @@
         } else {
             if (enableWindowContextResourcesUpdateOnConfigChange()) {
                 // There is no activity callback - update resources for window token, if needed.
-                final WindowTokenClient windowTokenClient = getWindowTokenClient();
-                if (windowTokenClient != null) {
-                    windowTokenClient.onConfigurationChanged(
+                final IBinder windowContextToken = mContext.getWindowContextToken();
+                if (windowContextToken instanceof WindowTokenClient) {
+                    WindowTokenClientController.getInstance().onWindowConfigurationChanged(
+                            windowContextToken,
                             mLastReportedMergedConfiguration.getMergedConfiguration(),
-                            newDisplayId == INVALID_DISPLAY ? mDisplay.getDisplayId()
-                                    : newDisplayId);
+                            newDisplayId == INVALID_DISPLAY
+                                    ? mDisplay.getDisplayId()
+                                    : newDisplayId
+                    );
                 }
             }
             updateConfiguration(newDisplayId);
@@ -6627,11 +6630,6 @@
         mForceNextConfigUpdate = false;
     }
 
-    private WindowTokenClient getWindowTokenClient() {
-        if (!(mContext instanceof WindowContext)) return null;
-        return (WindowTokenClient) mContext.getWindowContextToken();
-    }
-
     /**
      * Update display and views if last applied merged configuration changed.
      * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index edfa1d5..db699d7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1531,15 +1531,6 @@
      */
     @TestApi
     static boolean hasWindowExtensionsEnabled() {
-        if (!Flags.enableWmExtensionsForAllFlag() && ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15) {
-            // Since enableWmExtensionsForAllFlag, HAS_WINDOW_EXTENSIONS_ON_DEVICE is now true
-            // on all devices by default as a build file property.
-            // Until finishing flag ramp up, only return true when
-            // ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 is false, which is set per device by
-            // OEMs.
-            return false;
-        }
-
         if (!HAS_WINDOW_EXTENSIONS_ON_DEVICE) {
             return false;
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 8a10979..578b7b6 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -541,6 +541,22 @@
             "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
 
     /**
+     * Argument for specifying the extended selection.
+     *
+     * <p><strong>Type:</strong> {@link AccessibilityNodeInfo.Selection}<br>
+     * <strong>Actions:</strong>
+     *
+     * <ul>
+     *   <li>{@link AccessibilityAction#ACTION_SET_EXTENDED_SELECTION}
+     * </ul>
+     *
+     * @see AccessibilityAction#ACTION_SET_EXTENDED_SELECTION
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+    public static final String ACTION_ARGUMENT_SELECTION_PARCELABLE =
+            "android.view.accessibility.action.ARGUMENT_SELECTION_PARCELABLE";
+
+    /**
      * Argument for whether when moving at granularity to extend the selection
      * or to move it otherwise.
      * <p>
@@ -1146,6 +1162,8 @@
 
     private int mConnectionId = UNDEFINED_CONNECTION_ID;
 
+    private Selection mSelection;
+
     private RangeInfo mRangeInfo;
     private CollectionInfo mCollectionInfo;
     private CollectionItemInfo mCollectionItemInfo;
@@ -2660,6 +2678,56 @@
     }
 
     /**
+     * Sets the extended selection, which is a representation of selection that spans multiple nodes
+     * that exist within the subtree of the node defining selection.
+     *
+     * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
+     * should be constructed with {@code this} node or a descendant of it.
+     *
+     * <p><b>Note:</b> {@link AccessibilityNodeInfo#setFocusable} and {@link
+     * AccessibilityNodeInfo#setFocused} should both be called with {@code true} before setting the
+     * selection in order to make {@code this} node a candidate to contain a selection.
+     *
+     * <p><b>Note:</b> Cannot be called from an AccessibilityService. This class is made immutable
+     * before being delivered to an AccessibilityService.
+     *
+     * @param selection The extended selection within the node's subtree, or {@code null} if no
+     *     selection exists.
+     * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_EXTENDED_SELECTION
+     * @throws IllegalStateException If called from an AccessibilityService
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+    public void setSelection(@Nullable Selection selection) {
+        enforceNotSealed();
+        mSelection = selection;
+    }
+
+    /**
+     * Gets the extended selection, which is a representation of selection that spans multiple nodes
+     * that exist within the subtree of the node defining selection.
+     *
+     * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
+     * should be constructed with {@code this} node or a descendant of it.
+     *
+     * <p><b>Note:</b> In order for a node to be a candidate to contain a selection, {@link
+     * AccessibilityNodeInfo#isFocusable()} ()} and {@link AccessibilityNodeInfo#isFocused()} should
+     * both be return with {@code true}.
+     *
+     * @return The extended selection within the node's subtree, or {@code null} if no selection
+     *     exists.
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+    public @Nullable Selection getSelection() {
+        if (mSelection != null) {
+            mSelection.getStart().setWindowId(mWindowId);
+            mSelection.getStart().setConnectionId(mConnectionId);
+            mSelection.getEnd().setWindowId(mWindowId);
+            mSelection.getEnd().setConnectionId(mConnectionId);
+        }
+        return mSelection;
+    }
+
+    /**
      * Gets whether this node is visible to the user.
      * <p>
      * Between {@link Build.VERSION_CODES#JELLY_BEAN API 16} and
@@ -4168,6 +4236,15 @@
      *         there is no text selection and no cursor.
      */
     public int getTextSelectionStart() {
+        if (Flags.a11ySelectionApi()) {
+            Selection current = getSelection();
+            if ((current != null)
+                    && current.getStart().usesNode(this)
+                    && current.getEnd().usesNode(this)) {
+                return current.getStart().getOffset();
+            }
+            return UNDEFINED_SELECTION_INDEX;
+        }
         return mTextSelectionStart;
     }
 
@@ -4183,6 +4260,15 @@
      *         there is no text selection and no cursor.
      */
     public int getTextSelectionEnd() {
+        if (Flags.a11ySelectionApi()) {
+            Selection current = getSelection();
+            if ((current != null)
+                    && current.getStart().usesNode(this)
+                    && current.getEnd().usesNode(this)) {
+                return current.getEnd().getOffset();
+            }
+            return UNDEFINED_SELECTION_INDEX;
+        }
         return mTextSelectionEnd;
     }
 
@@ -4201,6 +4287,13 @@
      */
     public void setTextSelection(int start, int end) {
         enforceNotSealed();
+        if (Flags.a11ySelectionApi()) {
+            Selection selection =
+                    new Selection(
+                            new SelectionPosition(this, start), new SelectionPosition(this, end));
+            setSelection(selection);
+            return;
+        }
         mTextSelectionStart = start;
         mTextSelectionEnd = end;
     }
@@ -4875,6 +4968,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mSelection, DEFAULT.mSelection)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (mChecked != DEFAULT.mChecked) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -5055,6 +5152,9 @@
             parcel.writeLong(mLeashedParentNodeId);
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mSelection.writeToParcel(parcel, flags);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
             parcel.writeInt(mChecked);
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -5172,6 +5272,17 @@
         ExtraRenderingInfo ti = other.mExtraRenderingInfo;
         mExtraRenderingInfo = (ti == null) ? null
                 : new ExtraRenderingInfo(ti);
+
+        if (Flags.a11ySelectionApi()) {
+            if (other.getSelection() != null) {
+                SelectionPosition sps = other.getSelection().getStart();
+                SelectionPosition spe = other.getSelection().getEnd();
+                mSelection =
+                        new Selection(
+                                new SelectionPosition(sps.mSourceNodeId, sps.getOffset()),
+                                new SelectionPosition(spe.mSourceNodeId, spe.getOffset()));
+            }
+        }
     }
 
     /**
@@ -5344,6 +5455,9 @@
             mLeashedParentNodeId = parcel.readLong();
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mSelection = Selection.CREATOR.createFromParcel(parcel);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
             mChecked = parcel.readInt();
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -5495,6 +5609,9 @@
                 if (action == R.id.accessibilityActionScrollInDirection) {
                     return "ACTION_SCROLL_IN_DIRECTION";
                 }
+                if (action == R.id.accessibilityActionSetExtendedSelection) {
+                    return "ACTION_SET_EXTENDED_SELECTION";
+                }
                 return "ACTION_UNKNOWN";
             }
         }
@@ -5696,6 +5813,271 @@
     }
 
     /**
+     * A class which defines either the start or end of a selection that can span across multiple
+     * AccessibilityNodeInfo objects.
+     *
+     * @see AccessibilityNodeInfo.Selection
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+    public static final class SelectionPosition implements Parcelable {
+
+        private final int mOffset;
+        private final long mSourceNodeId;
+        private int mConnectionId;
+        private int mWindowId;
+
+        /**
+         * Instantiates a new SelectionPosition.
+         *
+         * @param node The {@link AccessibilityNodeInfo} for the node of this selection.
+         * @param offset The offset for a {@link SelectionPosition} within {@code view}'s text
+         *     content, which should be a value between 0 and the length of {@code view}'s text.
+         */
+        public SelectionPosition(@NonNull AccessibilityNodeInfo node, int offset) {
+            this(node.mSourceNodeId, offset);
+        }
+
+        /**
+         * Instantiates a new SelectionPosition.
+         *
+         * @param view The {@link View} containing the virtual descendant associated with the
+         *     selection position.
+         * @param offset The offset for a selection position within {@code view}'s text content,
+         *     which should be a value between 0 and the length of {@code view}'s text.
+         */
+        public SelectionPosition(@NonNull View view, int offset) {
+            this(
+                    makeNodeId(
+                            view.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
+                    offset);
+        }
+
+        /**
+         * Instantiates a new {@link SelectionPosition}.
+         *
+         * @param view The view whose virtual descendant is associated with the selection position.
+         * @param virtualDescendantId The ID of the virtual descendant within {@code view}'s virtual
+         *     subtree that contains the selection position.
+         * @param offset The offset for a selection position within the virtual descendant's text
+         *     content, which should be a value between 0 and the length of the descendant's text.
+         * @see AccessibilityNodeProvider
+         */
+        public SelectionPosition(@NonNull View view, int virtualDescendantId, int offset) {
+            this(makeNodeId(view.getAccessibilityViewId(), virtualDescendantId), offset);
+        }
+
+        private SelectionPosition(long sourceNodeId, int offset) {
+            mOffset = offset;
+            mSourceNodeId = sourceNodeId;
+        }
+
+        private SelectionPosition(Parcel in) {
+            mOffset = in.readInt();
+            mSourceNodeId = in.readLong();
+        }
+
+        private void setWindowId(int windowId) {
+            mWindowId = windowId;
+        }
+
+        private void setConnectionId(int connectionId) {
+            mConnectionId = connectionId;
+        }
+
+        /**
+         * Gets the node for {@code this} {@link SelectionPosition}
+         * <br>
+         * <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
+         *
+         * @return The node associated with {@code this} {@link SelectionPosition}
+         */
+        public @Nullable AccessibilityNodeInfo getNode() {
+            return getNodeForAccessibilityId(mConnectionId, mWindowId, mSourceNodeId);
+        }
+
+        /**
+         * Gets the offset for {@code this} {@link SelectionPosition}.
+         *
+         * @return A value from 0 to the length of {@link #getNode()}'s content representing the
+         *     offset of the {@link SelectionPosition}
+         */
+        public int getOffset() {
+            return mOffset;
+        }
+
+        private boolean usesNode(@NonNull AccessibilityNodeInfo node) {
+            return this.mSourceNodeId == node.mSourceNodeId
+                    && this.mConnectionId == node.mConnectionId
+                    && this.mWindowId == node.mWindowId;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null) {
+                return false;
+            }
+
+            if (other == this) {
+                return true;
+            }
+
+            if (getClass() != other.getClass()) {
+                return false;
+            }
+
+            SelectionPosition rhs = (SelectionPosition) other;
+            if (getOffset() != rhs.getOffset()) {
+                return false;
+            }
+
+            return mSourceNodeId == rhs.mSourceNodeId;
+        }
+
+        @Override
+        public int hashCode() {
+            final long prime = 877;
+            long result = 1;
+
+            if (mOffset != 0) {
+                result *= mOffset;
+            }
+
+            if (mSourceNodeId != UNDEFINED_NODE_ID) {
+                result *= mSourceNodeId;
+            }
+
+            return Long.hashCode(result * prime);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mOffset);
+            dest.writeLong(mSourceNodeId);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /**
+         * @see android.os.Parcelable.Creator
+         */
+        @NonNull
+        public static final Creator<SelectionPosition> CREATOR =
+                new Creator<SelectionPosition>() {
+                    @Override
+                    public SelectionPosition createFromParcel(Parcel in) {
+                        return new SelectionPosition(in);
+                    }
+
+                    @Override
+                    public SelectionPosition[] newArray(int size) {
+                        return new SelectionPosition[size];
+                    }
+                };
+    }
+
+    /**
+     * Represents a selection of content that may extend across more than one {@link
+     * AccessibilityNodeInfo} instance.
+     *
+     * @see AccessibilityNodeInfo.SelectionPosition
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+    public static final class Selection implements Parcelable {
+
+        private final SelectionPosition mStart;
+        private final SelectionPosition mEnd;
+
+        /**
+         * Instantiates a new Selection.
+         *
+         * @param start The start of the extended selection.
+         * @param end The end of the extended selection.
+         */
+        public Selection(@NonNull SelectionPosition start, @NonNull SelectionPosition end) {
+            this.mStart = start;
+            this.mEnd = end;
+        }
+
+        private Selection(Parcel in) {
+            mStart = SelectionPosition.CREATOR.createFromParcel(in);
+            mEnd = SelectionPosition.CREATOR.createFromParcel(in);
+        }
+
+        /**
+         * @return The start of the extended selection.
+         */
+        public @NonNull SelectionPosition getStart() {
+            return mStart;
+        }
+
+        /**
+         * @return The end of the extended selection.
+         */
+        public @NonNull SelectionPosition getEnd() {
+            return mEnd;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+
+            if (obj == this) {
+                return true;
+            }
+
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+
+            Selection rhs = (Selection) obj;
+            return getStart().equals(rhs.getStart()) && getEnd().equals(rhs.getEnd());
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 17;
+            return prime * getStart().hashCode() * getEnd().hashCode();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            mStart.writeToParcel(dest, flags);
+            mEnd.writeToParcel(dest, flags);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /**
+         * @see android.os.Parcelable.Creator
+         */
+        @NonNull
+        public static final Creator<Selection> CREATOR =
+                new Creator<Selection>() {
+                    @Override
+                    public Selection createFromParcel(Parcel in) {
+                        return new Selection(in);
+                    }
+
+                    @Override
+                    public Selection[] newArray(int size) {
+                        return new Selection[size];
+                    }
+                };
+    }
+
+    /**
      * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
      * Each action has a unique id that is mandatory and optional data.
      * <p>
@@ -6419,6 +6801,29 @@
         @NonNull public static final AccessibilityAction ACTION_SHOW_TEXT_SUGGESTIONS =
                 new AccessibilityAction(R.id.accessibilityActionShowTextSuggestions);
 
+        /**
+         * Action to set the extended selection. Performing this action with no arguments clears the
+         * selection.
+         *
+         * <p><strong>Arguments:</strong> {@link
+         * AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_PARCELABLE
+         * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_PARCELABLE}<br>
+         * <strong>Example:</strong> <code><pre><p>
+         *  Bundle arguments = new Bundle();
+         *  Selection selection = new Selection(null, null);
+         *  arguments.setParcelable(
+         *          AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_PARCELABLE, selection);
+         *  info.performAction(
+         *          AccessibilityAction.ACTION_SET_EXTENDED_SELECTION.getId(), arguments);
+         * </pre></code>
+         *
+         * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_PARCELABLE
+         */
+        @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+        @NonNull
+        public static final AccessibilityAction ACTION_SET_EXTENDED_SELECTION =
+                new AccessibilityAction(R.id.accessibilityActionSetExtendedSelection);
+
         private final int mActionId;
         private final CharSequence mLabel;
 
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2fb78c0..b66020b 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -57,6 +57,7 @@
 import android.view.ViewStructure;
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentcapture.flags.Flags;
 import android.view.contentprotection.ContentProtectionEventProcessor;
 import android.view.inputmethod.BaseInputConnection;
 
@@ -1008,6 +1009,9 @@
                     }
                 }
                 internalNotifyViewTreeEvent(sessionId, /* started= */ false);
+                if (Flags.flushAfterEachFrame()) {
+                    internalNotifySessionFlushEvent(sessionId);
+                }
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index e7bc004..8c98fa4 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -15,3 +15,14 @@
     bug: "380381249"
     is_exported: true
 }
+
+flag {
+    name: "flush_after_each_frame"
+    namespace: "pixel_state_server"
+    description: "Feature flag to send a flush event after each frame"
+    bug: "380381249"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0fb8042..56f0415 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3778,8 +3778,32 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             if (Flags.refactorInsetsController()) {
-                mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
-                        false /* fromIme */, statsToken);
+                synchronized (mH) {
+                    Handler vh = rootView.getHandler();
+                    if (vh == null) {
+                        // If the view doesn't have a handler, something has changed out from
+                        // under us.
+                        ImeTracker.forLogging().onFailed(statsToken,
+                                ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+                        return;
+                    }
+                    ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+
+                    if (vh.getLooper() != Looper.myLooper()) {
+                        // The view is running on a different thread than our own, so
+                        // we need to reschedule our work for over there.
+                        if (DEBUG) {
+                            Log.v(TAG, "Close current input: reschedule hide to view thread");
+                        }
+                        final var viewRootImpl = mCurRootView;
+                        vh.post(() -> viewRootImpl.getInsetsController().hide(
+                                WindowInsets.Type.ime(), false /* fromIme */, statsToken));
+                    } else {
+                        mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
+                                false /* fromIme */, statsToken);
+                    }
+                }
             } else {
                 IInputMethodManagerGlobalInvoker.hideSoftInput(
                         mClient,
diff --git a/core/java/android/window/ConfigurationDispatcher.java b/core/java/android/window/ConfigurationDispatcher.java
new file mode 100644
index 0000000..b8f0da1
--- /dev/null
+++ b/core/java/android/window/ConfigurationDispatcher.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 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.window;
+
+import android.annotation.NonNull;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+
+/**
+ * Indicates a {@link android.content.Context} could propagate the
+ * {@link android.content.res.Configuration} from the server side and users may listen to the
+ * updates through {@link android.content.Context#registerComponentCallbacks(ComponentCallbacks)}.
+ *
+ * @hide
+ */
+public interface ConfigurationDispatcher {
+
+    /**
+     * Called when there's configuration update from the server side.
+     */
+    void dispatchConfigurationChanged(@NonNull Configuration configuration);
+
+    /**
+     * Indicates that if this dispatcher should report the change even if it's not
+     * {@link Configuration#diffPublicOnly}.
+     */
+    default boolean shouldReportPrivateChanges() {
+        return false;
+    }
+}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 7852460..1ce5df7 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -55,6 +55,7 @@
             Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
     ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
             true),
+    ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
     ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
     ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
             Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 89327fe..bc5ad50 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -112,12 +113,21 @@
      */
     private final @ScreenOrientation int mOverrideOrientation;
 
+    /**
+     * {@link android.content.pm.ActivityInfo.Config} mask that specifies which
+     * configuration changes should trigger TaskFragment info change callbacks.
+     *
+     * @see android.content.pm.ActivityInfo.Config
+     */
+    private final @ActivityInfo.Config int mConfigurationChangeMask;
+
     private TaskFragmentCreationParams(
             @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
             @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
             @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
-            @ScreenOrientation int overrideOrientation) {
+            @ScreenOrientation int overrideOrientation,
+            @ActivityInfo.Config int configurationChangeMask) {
         if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
             throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
                     + " pairedActivityToken should not be set at the same time.");
@@ -131,6 +141,7 @@
         mPairedActivityToken = pairedActivityToken;
         mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
         mOverrideOrientation = overrideOrientation;
+        mConfigurationChangeMask = configurationChangeMask;
     }
 
     @NonNull
@@ -186,6 +197,11 @@
         return mOverrideOrientation;
     }
 
+    /** @hide */
+    public @ActivityInfo.Config int getConfigurationChangeMask() {
+        return mConfigurationChangeMask;
+    }
+
     private TaskFragmentCreationParams(Parcel in) {
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
@@ -196,6 +212,7 @@
         mPairedActivityToken = in.readStrongBinder();
         mAllowTransitionWhenEmpty = in.readBoolean();
         mOverrideOrientation = in.readInt();
+        mConfigurationChangeMask = in.readInt();
     }
 
     /** @hide */
@@ -210,6 +227,7 @@
         dest.writeStrongBinder(mPairedActivityToken);
         dest.writeBoolean(mAllowTransitionWhenEmpty);
         dest.writeInt(mOverrideOrientation);
+        dest.writeInt(mConfigurationChangeMask);
     }
 
     @NonNull
@@ -238,6 +256,7 @@
                 + " pairedActivityToken=" + mPairedActivityToken
                 + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
                 + " overrideOrientation=" + mOverrideOrientation
+                + " configurationChangeMask=" + mConfigurationChangeMask
                 + "}";
     }
 
@@ -275,6 +294,8 @@
 
         private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
+        private @ActivityInfo.Config int mConfigurationChangeMask = 0;
+
         public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
@@ -369,12 +390,30 @@
             return this;
         }
 
+        /**
+         * Sets {@link android.content.pm.ActivityInfo.Config} mask that specifies which
+         * configuration changes should trigger TaskFragment info change callbacks.
+         *
+         * Only system organizers are allowed to configure this value. This value is ignored for
+         * non-system organizers.
+         *
+         * @see android.content.pm.ActivityInfo.Config
+         * @hide
+         */
+        @NonNull
+        public Builder setConfigurationChangeMask(
+                @ActivityInfo.Config int configurationChangeMask) {
+            mConfigurationChangeMask = configurationChangeMask;
+            return this;
+        }
+
         /** Constructs the options to create TaskFragment with. */
         @NonNull
         public TaskFragmentCreationParams build() {
             return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
                     mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
-                    mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
+                    mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation,
+                    mConfigurationChangeMask);
         }
     }
 }
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 84a8b8f..778ccaf 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -17,8 +17,6 @@
 
 import static android.view.WindowManagerImpl.createWindowContextWindowManager;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
@@ -46,7 +44,8 @@
  * @hide
  */
 @UiContext
-public class WindowContext extends ContextWrapper implements WindowProvider {
+public class WindowContext extends ContextWrapper implements WindowProvider,
+        ConfigurationDispatcher {
     private final WindowManager mWindowManager;
     @WindowManager.LayoutParams.WindowType
     private final int mType;
@@ -155,7 +154,7 @@
     }
 
     /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
-    @VisibleForTesting(visibility = PACKAGE)
+    @Override
     public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
         mCallbacksController.dispatchConfigurationChanged(newConfig);
     }
@@ -170,4 +169,10 @@
     public Bundle getWindowContextOptions() {
         return mOptions;
     }
+
+    @Override
+    public boolean shouldReportPrivateChanges() {
+        // Always dispatch config changes to WindowContext.
+        return true;
+    }
 }
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 1e2f454..d31e43f 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -86,7 +86,6 @@
      * @param token The token used to attach to a window manager node. It is usually from
      *              {@link Context#getWindowContextToken()}.
      */
-    @VisibleForTesting
     public WindowContextController(@NonNull WindowTokenClient token) {
         mToken = token;
     }
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index c81c9ec..8468867 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -49,9 +49,11 @@
  *
  * @hide
  */
+@SuppressWarnings("HiddenSuperclass")
 @TestApi
 @UiContext
-public abstract class WindowProviderService extends Service implements WindowProvider {
+public abstract class WindowProviderService extends Service implements WindowProvider,
+        ConfigurationDispatcher {
 
     private static final String TAG = WindowProviderService.class.getSimpleName();
 
@@ -240,4 +242,14 @@
         mController.detachIfNeeded();
         mCallbacksController.clearCallbacks();
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public void dispatchConfigurationChanged(@NonNull Configuration configuration) {
+        onConfigurationChanged(configuration);
+    }
 }
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index f7bee61..9b296c8 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -107,6 +107,7 @@
      * @param newDisplayId the updated {@link android.view.Display} ID
      */
     @MainThread
+    @VisibleForTesting(visibility = PACKAGE)
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
         onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
     }
@@ -120,8 +121,6 @@
                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
     }
 
-    // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
-    //  are inherited from WindowProvider.
     /**
      * Called when {@link Configuration} updates from the server side receive.
      *
@@ -168,7 +167,7 @@
         CompatibilityInfo.applyOverrideIfNeeded(newConfig);
         final boolean displayChanged;
         final boolean shouldUpdateResources;
-        final int diff;
+        final int publicDiff;
         final Configuration currentConfig;
 
         synchronized (mConfiguration) {
@@ -176,7 +175,7 @@
             shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
                     newConfig, newConfig /* overrideConfig */, displayChanged,
                     null /* configChanged */);
-            diff = mConfiguration.diffPublicOnly(newConfig);
+            publicDiff = mConfiguration.diffPublicOnly(newConfig);
             currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null;
             if (shouldUpdateResources) {
                 mConfiguration.setTo(newConfig);
@@ -199,17 +198,15 @@
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
 
-            if (shouldReportConfigChange && context instanceof WindowContext) {
-                final WindowContext windowContext = (WindowContext) context;
-                windowContext.dispatchConfigurationChanged(newConfig);
+            if (shouldReportConfigChange && context instanceof ConfigurationDispatcher dispatcher) {
+                // Updating resources implies some fields of configuration are updated despite they
+                // are public or not.
+                if (dispatcher.shouldReportPrivateChanges() || publicDiff != 0) {
+                    dispatcher.dispatchConfigurationChanged(newConfig);
+                }
             }
 
-            if (shouldReportConfigChange && diff != 0
-                    && context instanceof WindowProviderService) {
-                final WindowProviderService windowProviderService = (WindowProviderService) context;
-                windowProviderService.onConfigurationChanged(newConfig);
-            }
-            freeTextLayoutCachesIfNeeded(diff);
+            freeTextLayoutCachesIfNeeded(publicDiff);
             if (mShouldDumpConfigForIme) {
                 if (!shouldReportConfigChange) {
                     Log.d(TAG, "Only apply configuration update to Resources because "
@@ -218,7 +215,7 @@
                             + ", config=" + context.getResources().getConfiguration()
                             + ", display ID=" + context.getDisplayId() + "\n"
                             + Debug.getCallers(5));
-                } else if (diff == 0) {
+                } else if (publicDiff == 0) {
                     Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
                             + " public difference with updated config. "
                             + " Current config=" + context.getResources().getConfiguration()
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index fcd7dfb..72278d9 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -25,7 +25,9 @@
 import android.app.servertransaction.WindowContextInfoChangeItem;
 import android.app.servertransaction.WindowContextWindowRemovalItem;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArraySet;
@@ -50,6 +52,7 @@
     private final Object mLock = new Object();
     private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
             .getApplicationThread();
+    private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
 
     /** Attached {@link WindowTokenClient}. */
     @GuardedBy("mLock")
@@ -257,6 +260,20 @@
         }
     }
 
+    /** Propagates the configuration change to the client token. */
+    public void onWindowConfigurationChanged(@NonNull IBinder clientToken,
+            @NonNull Configuration config, int displayId) {
+        final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
+        if (windowTokenClient != null) {
+            // Let's make sure it's called on the main thread!
+            if (mHandler.getLooper().isCurrentThread()) {
+                windowTokenClient.onConfigurationChanged(config, displayId);
+            } else {
+                windowTokenClient.postOnConfigurationChanged(config, displayId);
+            }
+        }
+    }
+
     @Nullable
     private WindowTokenClient getWindowTokenClientIfAttached(@NonNull IBinder clientToken) {
         if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 509e084..b805ac5 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -695,4 +695,14 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_desktop_close_shortcut_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Fix the window-close keyboard shortcut in Desktop Mode."
+    bug: "394599430"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ac6625b..54d0eef 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -55,14 +55,6 @@
 
 flag {
     namespace: "windowing_sdk"
-    name: "enable_wm_extensions_for_all_flag"
-    description: "Whether to enable WM Extensions for all devices"
-    bug: "306666082"
-    is_fixed_read_only: true
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "activity_embedding_animation_customization_flag"
     description: "Whether the animation customization feature for AE is enabled"
     bug: "293658614"
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e60879e..38dc198 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -429,6 +429,27 @@
                     null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
         }
 
+        if (Flags.enableMediaAndLocationPreload()) {
+            // As these libraries are technically optional and not necessarily inherited from
+            // base_system.mk, only cache them if they exist.
+            final String mediaJarPath = "/system/framework/com.android.media.remotedisplay.jar";
+            if (new File(mediaJarPath).exists()) {
+                libs.add(new SharedLibraryInfo(
+                        mediaJarPath, null /*packageName*/,
+                        null /*codePaths*/, null /*name*/, 0 /*version*/,
+                        SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+                        null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+            }
+            final String locationJarPath = "/system/framework/com.android.location.provider.jar";
+            if (new File(locationJarPath).exists()) {
+                libs.add(new SharedLibraryInfo(
+                        locationJarPath, null /*packageName*/,
+                        null /*codePaths*/, null /*name*/, 0 /*version*/,
+                        SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+                        null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+            }
+        }
+
         // WindowManager Extensions is an optional shared library that is required for WindowManager
         // Jetpack to fully function. Since it is a widely used library, preload it to improve apps
         // startup performance.
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 25a9fbc..32cde50 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -53,6 +53,13 @@
 }
 
 flag {
+    name: "enable_media_and_location_preload"
+    namespace: "system_performance"
+    description: "Enables zygote preload of non-BCP media and location libraries."
+    bug: "241474956"
+}
+
+flag {
     name: "use_transaction_codes_for_unknown_methods"
     namespace: "stability"
     description: "Use transaction codes when the method names is unknown"
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 445dac7..21d000d 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.pm.pkg.component;
 
+import static android.provider.flags.Flags.newStoragePublicApi;
 import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
 
 import android.aconfig.DeviceProtos;
@@ -27,6 +28,7 @@
 import android.content.res.Flags;
 import android.os.Environment;
 import android.os.Process;
+import android.os.flagging.AconfigPackage;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.Xml;
@@ -43,6 +45,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * A class that manages a cache of all device feature flags and their default + override values.
@@ -58,7 +61,8 @@
     private static final String OVERRIDE_PREFIX = "device_config_overrides/";
     private static final String STAGED_PREFIX = "staged/";
 
-    private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+    private final Map<String, Boolean> mFlagValues = new ArrayMap<>();
+    private final Map<String, AconfigPackage> mAconfigPackages = new ConcurrentHashMap<>();
 
     public AconfigFlags() {
         if (!Flags.manifestFlagging()) {
@@ -67,21 +71,31 @@
             }
             return;
         }
-        final var defaultFlagProtoFiles =
-                (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
-                        : Arrays.asList(DeviceProtos.PATHS);
-        for (String fileName : defaultFlagProtoFiles) {
-            try (var inputStream = new FileInputStream(fileName)) {
-                loadAconfigDefaultValues(inputStream.readAllBytes());
-            } catch (IOException e) {
-                Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+
+        if (useNewStorage()) {
+            Slog.i(LOG_TAG, "Using new flag storage");
+        } else {
+            Slog.i(LOG_TAG, "Using OLD proto flag storage");
+            final var defaultFlagProtoFiles =
+                    (Process.myUid() == Process.SYSTEM_UID) ? DeviceProtos.parsedFlagsProtoPaths()
+                            : Arrays.asList(DeviceProtos.PATHS);
+            for (String fileName : defaultFlagProtoFiles) {
+                try (var inputStream = new FileInputStream(fileName)) {
+                    loadAconfigDefaultValues(inputStream.readAllBytes());
+                } catch (IOException e) {
+                    Slog.w(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+                }
+            }
+            if (Process.myUid() == Process.SYSTEM_UID) {
+                // Server overrides are only accessible to the system, no need to even try loading
+                // them in user processes.
+                loadServerOverrides();
             }
         }
-        if (Process.myUid() == Process.SYSTEM_UID) {
-            // Server overrides are only accessible to the system, no need to even try loading them
-            // in user processes.
-            loadServerOverrides();
-        }
+    }
+
+    private static boolean useNewStorage() {
+        return newStoragePublicApi() && Flags.useNewAconfigStorage();
     }
 
     private void loadServerOverrides() {
@@ -200,7 +214,40 @@
      */
     @Nullable
     public Boolean getFlagValue(@NonNull String flagPackageAndName) {
-        Boolean value = mFlagValues.get(flagPackageAndName);
+        if (useNewStorage()) {
+            return getFlagValueFromNewStorage(flagPackageAndName);
+        } else {
+            Boolean value = mFlagValues.get(flagPackageAndName);
+            if (DEBUG) {
+                Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+            }
+            return value;
+        }
+    }
+
+    private Boolean getFlagValueFromNewStorage(String flagPackageAndName) {
+        int index = flagPackageAndName.lastIndexOf('.');
+        if (index < 0) {
+            Slog.e(LOG_TAG, "Unable to parse package name from " + flagPackageAndName);
+            return null;
+        }
+        String flagPackage = flagPackageAndName.substring(0, index);
+        String flagName = flagPackageAndName.substring(index + 1);
+        Boolean value = null;
+        AconfigPackage aconfigPackage = mAconfigPackages.computeIfAbsent(flagPackage, p -> {
+            try {
+                return AconfigPackage.load(p);
+            } catch (Exception e) {
+                Slog.e(LOG_TAG, "Failed to load aconfig package " + p, e);
+                return null;
+            }
+        });
+        if (aconfigPackage != null) {
+            // Default value is false for when the flag is not found.
+            // Note: Unlike with the old storage, with AconfigPackage, we don't have a way to
+            // know if the flag is not found or if it's found but the value is false.
+            value = aconfigPackage.getBooleanFlagValue(flagName, false);
+        }
         if (DEBUG) {
             Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
         }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 05a33fe..d8cf258 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -160,19 +160,21 @@
         Objects.requireNonNull(mConfigurationService,
                 "A null ProtoLog Configuration Service was provided!");
 
-        try {
-            var args = createConfigurationServiceRegisterClientArgs();
+        mBackgroundLoggingService.execute(() -> {
+            try {
+                var args = createConfigurationServiceRegisterClientArgs();
 
-            final var groupArgs = mLogGroups.values().stream()
-                    .map(group -> new RegisterClientArgs
-                            .GroupConfig(group.name(), group.isLogToLogcat()))
-                    .toArray(RegisterClientArgs.GroupConfig[]::new);
-            args.setGroups(groupArgs);
+                final var groupArgs = mLogGroups.values().stream()
+                        .map(group -> new RegisterClientArgs
+                                .GroupConfig(group.name(), group.isLogToLogcat()))
+                        .toArray(RegisterClientArgs.GroupConfig[]::new);
+                args.setGroups(groupArgs);
 
-            mConfigurationService.registerClient(this, args);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Failed to register ProtoLog client");
-        }
+                mConfigurationService.registerClient(this, args);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Failed to register ProtoLog client");
+            }
+        });
     }
 
     /**
diff --git a/core/java/com/android/internal/protolog/WmProtoLogGroups.java b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
index 4bd5d24..5edc2fb 100644
--- a/core/java/com/android/internal/protolog/WmProtoLogGroups.java
+++ b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
@@ -100,6 +100,8 @@
     WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     WM_DEBUG_EMBEDDED_WINDOWS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
+    WM_DEBUG_PRESENTATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 9a5849a..641ecc9 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -399,7 +399,9 @@
     @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
     public void setIsCollapsed(boolean isCollapsed) {
         mIsCollapsed = isCollapsed;
-        mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
+        mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed
+                ? TextUtils.isEmpty(mSummarizedContent) ? 1 : 2
+                : Integer.MAX_VALUE);
         updateExpandButton();
         updateContentEndPaddings();
     }
@@ -448,7 +450,7 @@
 
         List<MessagingMessage> newMessagingMessages;
         mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
-        if (mSummarizedContent != null && mIsCollapsed) {
+        if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
             Notification.MessagingStyle.Message summary =
                     new Notification.MessagingStyle.Message(mSummarizedContent,  0, "");
             newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -1162,7 +1164,7 @@
                 nameOverride = mNameReplacement;
             }
             newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed);
-            newGroup.setSingleLine(mIsCollapsed);
+            newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
             newGroup.setSender(sender, nameOverride);
             newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
             mGroups.add(newGroup);
@@ -1462,7 +1464,6 @@
                 maxHeight = Math.max(maxHeight,
                         child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
             }
-
             maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
             if (maxHeight != getMeasuredHeight()) {
                 setMeasuredDimension(getMeasuredWidth(), maxHeight);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 90ab660..e9d920c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -198,7 +198,8 @@
                 /* isHistoric= */true, usePrecomputedText);
         List<MessagingMessage> newMessagingMessages;
         mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
-        if (mSummarizedContent != null && mIsCollapsed) {
+        if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
+            mMessagingLinearLayout.setMaxDisplayedLines(2);
             Notification.MessagingStyle.Message summary =
                     new Notification.MessagingStyle.Message(mSummarizedContent,  0, "");
             newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -488,7 +489,7 @@
             if (sender != mUser && mNameReplacement != null) {
                 nameOverride = mNameReplacement;
             }
-            newGroup.setSingleLine(mIsCollapsed);
+            newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
             newGroup.setShowingAvatar(!mIsCollapsed);
             newGroup.setSender(sender, nameOverride);
             newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 5e82772..905d4dd 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -83,7 +83,7 @@
 
     /** @see R.styleable#NotificationProgressBar_trackerHeight */
     private final int mTrackerHeight;
-    private int mTrackerWidth;
+    private int mTrackerDrawWidth = 0;
     private int mTrackerPos;
     private final Matrix mMatrix = new Matrix();
     private Matrix mTrackerDrawMatrix = null;
@@ -157,7 +157,7 @@
         } else {
             // TODO: b/372908709 - maybe don't rerun the entire calculation every time the
             //  progress model is updated? For example, if the segments and parts aren't changed,
-            //  there is no need to call `processAndConvertToViewParts` again.
+            //  there is no need to call `processModelAndConvertToViewParts` again.
 
             final int progress = mProgressModel.getProgress();
             final int progressMax = mProgressModel.getProgressMax();
@@ -286,8 +286,11 @@
     private void configureTrackerBounds() {
         // Reset the tracker draw matrix to null
         mTrackerDrawMatrix = null;
+        mTrackerDrawWidth = 0;
 
-        if (mTracker == null || mTrackerHeight <= 0) {
+        if (mTracker == null) return;
+        if (mTrackerHeight <= 0) {
+            mTrackerDrawWidth = mTracker.getIntrinsicWidth();
             return;
         }
 
@@ -306,14 +309,14 @@
         if (dWidth > maxDWidth) {
             scale = (float) mTrackerHeight / (float) dHeight;
             dx = (maxDWidth * scale - dWidth * scale) * 0.5f;
-            mTrackerWidth = (int) (maxDWidth * scale);
+            mTrackerDrawWidth = (int) (maxDWidth * scale);
         } else if (dHeight > maxDHeight) {
             scale = (float) mTrackerHeight * 0.5f / (float) dWidth;
             dy = (maxDHeight * scale - dHeight * scale) * 0.5f;
-            mTrackerWidth = mTrackerHeight / 2;
+            mTrackerDrawWidth = mTrackerHeight / 2;
         } else {
             scale = (float) mTrackerHeight / (float) dHeight;
-            mTrackerWidth = (int) (dWidth * scale);
+            mTrackerDrawWidth = (int) (dWidth * scale);
         }
 
         mTrackerDrawMatrix.setScale(scale, scale);
@@ -449,7 +452,8 @@
                 segSegGap,
                 segPointGap,
                 pointRadius,
-                mHasTrackerIcon
+                mHasTrackerIcon,
+                mTrackerDrawWidth
         );
 
         final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
@@ -465,7 +469,6 @@
                     segmentMinWidth,
                     pointRadius,
                     progressFraction,
-                    width,
                     isStyledByProgress,
                     progressGap
             );
@@ -493,8 +496,8 @@
                         pointRadius,
                         mHasTrackerIcon,
                         segmentMinWidth,
-                        isStyledByProgress
-                );
+                        isStyledByProgress,
+                        mTrackerDrawWidth);
             } catch (NotEnoughWidthToFitAllPartsException ex) {
                 Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
                         ex);
@@ -522,8 +525,8 @@
                         pointRadius,
                         mHasTrackerIcon,
                         segmentMinWidth,
-                        isStyledByProgress
-                );
+                        isStyledByProgress,
+                        mTrackerDrawWidth);
             } catch (NotEnoughWidthToFitAllPartsException ex) {
                 Log.w(TAG,
                         "Failed to stretch and rescale segments with single segments and no points",
@@ -537,16 +540,20 @@
                     mParts,
                     mProgressDrawableParts,
                     progressFraction,
-                    width,
                     isStyledByProgress,
                     progressGap);
         }
 
+        // Extend the first and last segments to fill the entire width.
+        p.first.getFirst().setStart(0);
+        p.first.getLast().setEnd(width);
+
         if (DEBUG) {
             Log.d(TAG, "Updating NotificationProgressDrawable parts");
         }
         mNotificationProgressDrawable.setParts(p.first);
-        mAdjustedProgressFraction = p.second / width;
+        mAdjustedProgressFraction =
+                (p.second - mTrackerDrawWidth / 2F) / (width - mTrackerDrawWidth);
     }
 
     private void updateTrackerAndBarPos(int w, int h) {
@@ -607,7 +614,7 @@
         int available = w - mPaddingLeft - mPaddingRight;
         final int trackerWidth = tracker.getIntrinsicWidth();
         final int trackerHeight = tracker.getIntrinsicHeight();
-        available -= ((mTrackerHeight <= 0) ? trackerWidth : mTrackerWidth);
+        available -= mTrackerDrawWidth;
 
         final int trackerPos = (int) (progressFraction * available + 0.5f);
 
@@ -672,7 +679,7 @@
         canvas.translate(mPaddingLeft + mTrackerPos, mPaddingTop);
 
         if (mTrackerHeight > 0) {
-            canvas.clipRect(0, 0, mTrackerWidth, mTrackerHeight);
+            canvas.clipRect(0, 0, mTrackerDrawWidth, mTrackerHeight);
         }
 
         if (mTrackerDrawMatrix != null) {
@@ -751,6 +758,7 @@
             throw new IllegalArgumentException("Invalid progress : " + progress);
         }
 
+
         for (ProgressStyle.Point point : points) {
             final int pos = point.getPosition();
             if (pos < 0 || pos > progressMax) {
@@ -758,6 +766,19 @@
             }
         }
 
+        // There should be no points at start or end. If there are, drop them with a warning.
+        points.removeIf(point -> {
+            final int pos = point.getPosition();
+            if (pos == 0) {
+                Log.w(TAG, "Dropping point at start");
+                return true;
+            } else if (pos == progressMax) {
+                Log.w(TAG, "Dropping point at end");
+                return true;
+            }
+            return false;
+        });
+
         final Map<Integer, ProgressStyle.Segment> startToSegmentMap = generateStartToSegmentMap(
                 segments);
         final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
@@ -891,12 +912,14 @@
             float segSegGap,
             float segPointGap,
             float pointRadius,
-            boolean hasTrackerIcon
-    ) {
+            boolean hasTrackerIcon,
+            int trackerDrawWidth) {
         List<DrawablePart> drawableParts = new ArrayList<>();
 
-        // generally, we will start drawing at (x, y) and end at (x+w, y)
-        float x = (float) 0;
+        float available = totalWidth - trackerDrawWidth;
+        // Generally, we will start the first segment at (x+trackerDrawWidth/2, y) and end the last
+        // segment at (x+w-trackerDrawWidth/2, y)
+        float x = trackerDrawWidth / 2F;
 
         final int nParts = parts.size();
         for (int iPart = 0; iPart < nParts; iPart++) {
@@ -904,15 +927,14 @@
             final Part prevPart = iPart == 0 ? null : parts.get(iPart - 1);
             final Part nextPart = iPart + 1 == nParts ? null : parts.get(iPart + 1);
             if (part instanceof Segment segment) {
-                final float segWidth = segment.mFraction * totalWidth;
+                final float segWidth = segment.mFraction * available;
                 // Advance the start position to account for a point immediately prior.
-                final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap,
-                        iPart == 1);
+                final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap);
                 final float start = x + startOffset;
                 // Retract the end position to account for the padding and a point immediately
                 // after.
                 final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
-                        segSegGap, iPart == nParts - 2, hasTrackerIcon);
+                        segSegGap, hasTrackerIcon);
                 final float end = x + segWidth - endOffset;
 
                 drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -927,16 +949,6 @@
                 final float pointWidth = 2 * pointRadius;
                 float start = x - pointRadius;
                 float end = x + pointRadius;
-                // Only shift the points right at the start/end.
-                // For the points close to the start/end, the segment minimum width requirement
-                // would take care of shifting them to be within the bounds.
-                if (iPart == 0) {
-                    start = 0;
-                    end = pointWidth;
-                } else if (iPart == nParts - 1) {
-                    start = totalWidth - pointWidth;
-                    end = totalWidth;
-                }
 
                 drawableParts.add(new DrawablePoint(start, end, point.mColor));
             }
@@ -945,16 +957,13 @@
         return drawableParts;
     }
 
-    private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
-            boolean isSecondPart) {
+    private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap) {
         if (!(prevPart instanceof Point)) return 0F;
-        final float pointOffset = isSecondPart ? pointRadius : 0;
-        return pointOffset + pointRadius + segPointGap;
+        return pointRadius + segPointGap;
     }
 
     private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
-            float segPointGap, float segSegGap, boolean isSecondToLastPart,
-            boolean hasTrackerIcon) {
+            float segPointGap, float segSegGap, boolean hasTrackerIcon) {
         if (nextPart == null) return 0F;
         if (nextPart instanceof Segment nextSeg) {
             if (!seg.mFaded && nextSeg.mFaded) {
@@ -964,8 +973,7 @@
             return segSegGap;
         }
 
-        final float pointOffset = isSecondToLastPart ? pointRadius : 0;
-        return segPointGap + pointRadius + pointOffset;
+        return segPointGap + pointRadius;
     }
 
     /**
@@ -980,7 +988,6 @@
             float segmentMinWidth,
             float pointRadius,
             float progressFraction,
-            float totalWidth,
             boolean isStyledByProgress,
             float progressGap
     ) throws NotEnoughWidthToFitAllPartsException {
@@ -1003,7 +1010,6 @@
                     parts,
                     drawableParts,
                     progressFraction,
-                    totalWidth,
                     isStyledByProgress,
                     progressGap);
         }
@@ -1056,7 +1062,6 @@
                 parts,
                 drawableParts,
                 progressFraction,
-                totalWidth,
                 isStyledByProgress,
                 progressGap);
     }
@@ -1071,11 +1076,12 @@
             List<Part> parts,
             List<DrawablePart> drawableParts,
             float progressFraction,
-            float totalWidth,
             boolean isStyledByProgress,
             float progressGap
     ) {
-        if (progressFraction == 1) return new Pair<>(drawableParts, totalWidth);
+        if (progressFraction == 1) {
+            return new Pair<>(drawableParts, drawableParts.getLast().getEnd());
+        }
 
         int iPartFirstSegmentToStyle = -1;
         int iPartSegmentToSplit = -1;
@@ -1162,14 +1168,15 @@
             float pointRadius,
             boolean hasTrackerIcon,
             float segmentMinWidth,
-            boolean isStyledByProgress
+            boolean isStyledByProgress,
+            int trackerDrawWidth
     ) throws NotEnoughWidthToFitAllPartsException {
         List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
         List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
-                segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                segSegGap, segPointGap, pointRadius, hasTrackerIcon, trackerDrawWidth);
         return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
-                getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+                getProgressFraction(progressMax, progress), isStyledByProgress,
                 hasTrackerIcon ? 0F : segSegGap);
     }
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 447822f..06702e2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -134,6 +134,10 @@
                 "android_app_ActivityThread.cpp",
                 "android_app_NativeActivity.cpp",
                 "android_app_admin_SecurityLog.cpp",
+                "android_media_ImageReader.cpp",
+                "android_media_ImageWriter.cpp",
+                "android_media_PublicFormatUtils.cpp",
+                "android_media_Utils.cpp",
                 "android_opengl_EGL14.cpp",
                 "android_opengl_EGL15.cpp",
                 "android_opengl_EGLExt.cpp",
@@ -531,3 +535,35 @@
         "vintf",
     ],
 }
+
+cc_library_shared {
+    name: "libmedia_jni_utils",
+    srcs: [
+        ":libgui_frame_event_aidl",
+        "android_media_Utils.cpp",
+    ],
+
+    header_libs: [
+        "libgui_headers",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libui",
+        "libutils",
+    ],
+
+    include_dirs: [
+        "system/media/camera/include",
+    ],
+
+    export_include_dirs: ["."],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-error=deprecated-declarations",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5c0b720..b2b8263 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -102,7 +102,10 @@
 extern int register_android_media_AudioProductStrategies(JNIEnv *env);
 extern int register_android_media_AudioVolumeGroups(JNIEnv *env);
 extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env);
+extern int register_android_media_ImageReader(JNIEnv *env);
+extern int register_android_media_ImageWriter(JNIEnv *env);
 extern int register_android_media_MicrophoneInfo(JNIEnv *env);
+extern int register_android_media_PublicFormatUtils(JNIEnv *env);
 extern int register_android_media_ToneGenerator(JNIEnv *env);
 extern int register_android_media_audio_common_AidlConversion(JNIEnv* env);
 extern int register_android_media_midi(JNIEnv *env);
@@ -1658,8 +1661,11 @@
         REG_JNI(register_android_media_AudioProductStrategies),
         REG_JNI(register_android_media_AudioVolumeGroups),
         REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+        REG_JNI(register_android_media_ImageReader),
+        REG_JNI(register_android_media_ImageWriter),
         REG_JNI(register_android_media_MediaMetrics),
         REG_JNI(register_android_media_MicrophoneInfo),
+        REG_JNI(register_android_media_PublicFormatUtils),
         REG_JNI(register_android_media_RemoteDisplay),
         REG_JNI(register_android_media_ToneGenerator),
         REG_JNI(register_android_media_audio_common_AidlConversion),
diff --git a/media/jni/android_media_ImageReader.cpp b/core/jni/android_media_ImageReader.cpp
similarity index 99%
rename from media/jni/android_media_ImageReader.cpp
rename to core/jni/android_media_ImageReader.cpp
index effa92c..20b9c57 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/core/jni/android_media_ImageReader.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#undef ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION // TODO:remove this and fix code
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ImageReader_JNI"
diff --git a/media/jni/android_media_ImageWriter.cpp b/core/jni/android_media_ImageWriter.cpp
similarity index 99%
rename from media/jni/android_media_ImageWriter.cpp
rename to core/jni/android_media_ImageWriter.cpp
index 93deb51..1357dd8 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/core/jni/android_media_ImageWriter.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#undef ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION // TODO:remove this and fix code
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ImageWriter_JNI"
diff --git a/media/jni/android_media_PublicFormatUtils.cpp b/core/jni/android_media_PublicFormatUtils.cpp
similarity index 100%
rename from media/jni/android_media_PublicFormatUtils.cpp
rename to core/jni/android_media_PublicFormatUtils.cpp
diff --git a/media/jni/android_media_Utils.cpp b/core/jni/android_media_Utils.cpp
similarity index 100%
rename from media/jni/android_media_Utils.cpp
rename to core/jni/android_media_Utils.cpp
diff --git a/media/jni/android_media_Utils.h b/core/jni/android_media_Utils.h
similarity index 100%
rename from media/jni/android_media_Utils.h
rename to core/jni/android_media_Utils.h
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9f4cdc..5104988 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -9385,6 +9385,10 @@
             android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.security.UpdateCertificateRevocationStatusJobService"
+            android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
             android:exported="false">
             <intent-filter>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d7ffcc5..17acf9a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7352,6 +7352,21 @@
      default on this device-->
     <string-array translatable="false" name="config_notificationDefaultUnsupportedAdjustments" />
 
+    <!-- Preference name of bugreport-->
+    <string name="prefs_bugreport" translatable="false">bugreports</string>
+
+    <!-- key value of warning state stored in bugreport preference-->
+    <string name="key_warning_state" translatable="false">warning-state</string>
+
+    <!-- Bugreport warning dialog state unknown-->
+    <integer name="bugreport_state_unknown">0</integer>
+
+    <!-- Bugreport warning dialog state shows the warning dialog-->
+    <integer name="bugreport_state_show">1</integer>
+
+    <!-- Bugreport warning dialog state skips the warning dialog-->
+    <integer name="bugreport_state_hide">2</integer>
+
     <!-- By default ActivityOptions#makeScaleUpAnimation is only used between activities. This
          config enables OEMs to support its usage across tasks.-->
     <bool name="config_enableCrossTaskScaleUpAnimation">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 595160e..e9d87e4 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -431,15 +431,18 @@
     <!-- The minimum height of the notification content (even when there's only one line of text) -->
     <dimen name="notification_2025_content_min_height">40dp</dimen>
 
-    <!-- Height of a headerless notification with one or two lines -->
-    <!-- 16 * 2 (margins) + 40 (min content height) = 72 (notification) -->
+    <!-- Max height of a collapsed (headerless) notification with a summarization -->
+    <dimen name="notification_collapsed_height_with_summarization">156dp</dimen>
+
+    <!-- Max height of a collapsed (headerless) notification with one or two lines -->
+    <!-- 16 * 2 (margins) + 48 (min content height) = 72 (notification) -->
     <dimen name="notification_2025_min_height">72dp</dimen>
 
     <!-- Height of a headerless notification with one line -->
     <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
     <dimen name="notification_headerless_min_height">56dp</dimen>
 
-    <!-- Height of a small two-line notification -->
+    <!-- Max height of a collapsed two-line notification -->
     <!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) -->
     <dimen name="notification_min_height">88dp</dimen>
 
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 3b39a65..5b5ef09 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -157,6 +157,9 @@
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
   <item type="id" name="accessibilityActionContextClick" />
 
+  <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_EXTENDED_SELECTION}. -->
+  <item type="id" name="accessibilityActionSetExtendedSelection" />
+
   <item type="id" name="remote_input_tag" />
   <item type="id" name="pending_intent_tag" />
   <item type="id" name="remote_checked_change_listener_tag" />
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 2d411d0..e3137e2 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -129,6 +129,8 @@
   <staging-public-group type="id" first-id="0x01b20000">
     <!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) -->
     <public name="remoteViewsMetricsId"/>
+    <!-- @FlaggedApi("android.view.accessibility.a11y_selection_api")  -->
+    <public name="accessibilityActionSetExtendedSelection"/>
   </staging-public-group>
 
   <staging-public-group type="style" first-id="0x01b10000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6701e63..da6fb3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5899,6 +5899,7 @@
 
   <java-symbol type="array" name="config_notificationDefaultUnsupportedAdjustments" />
   <java-symbol type="drawable" name="ic_notification_summarization" />
+  <java-symbol type="dimen" name="notification_collapsed_height_with_summarization" />
 
   <!-- Advanced Protection Service USB feature -->
   <java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_title" />
@@ -5906,6 +5907,12 @@
   <java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_title" />
   <java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_text" />
 
+  <java-symbol type="string" name="prefs_bugreport" />
+  <java-symbol type="string" name="key_warning_state" />
+  <java-symbol type="integer" name="bugreport_state_unknown" />
+  <java-symbol type="integer" name="bugreport_state_show" />
+  <java-symbol type="integer" name="bugreport_state_hide" />
+
   <!-- Enable OEMs to support scale up anim across tasks.-->
   <java-symbol type="bool" name="config_enableCrossTaskScaleUpAnimation" />
 
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ca6ad6f..f89e441 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2532,6 +2532,46 @@
 
     @Test
     @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_addProgressPoint_dropsZeroPoints() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // Points should not be a negative integer.
+        progressStyle
+                .addProgressPoint(new Notification.ProgressStyle.Point(0));
+
+        // THEN
+        assertThat(progressStyle.getProgressPoints()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_setProgressPoint_dropsZeroPoints() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // Points should not be a negative integer.
+        progressStyle
+                .setProgressPoints(List.of(new Notification.ProgressStyle.Point(0)));
+
+        // THEN
+        assertThat(progressStyle.getProgressPoints()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_createProgressModel_ignoresPointsAtMax() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+        // Points should not larger than progress maximum.
+        progressStyle
+                .addProgressPoint(new Notification.ProgressStyle.Point(100));
+
+        // THEN
+        assertThat(progressStyle.createProgressModel(Color.BLUE, Color.RED).getPoints()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
     public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
         // GIVEN
         final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2573,14 +2613,14 @@
         // THEN
         assertThat(progressStyle.createProgressModel(defaultProgressColor, backgroundColor)
                 .getPoints()).isEqualTo(
-                        List.of(new Notification.ProgressStyle.Point(0)
-                                .setColor(expectedProgressColor),
-                                new Notification.ProgressStyle.Point(20)
+                        List.of(new Notification.ProgressStyle.Point(20)
                                 .setColor(expectedProgressColor),
                                 new Notification.ProgressStyle.Point(50)
                                 .setColor(expectedProgressColor),
                                 new Notification.ProgressStyle.Point(70)
-                                .setColor(expectedProgressColor)
+                                .setColor(expectedProgressColor),
+                                new Notification.ProgressStyle.Point(80)
+                                        .setColor(expectedProgressColor)
                         )
         );
     }
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 8fa5103..dc2f0a6 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,8 +307,10 @@
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
                 mDisplayManagerGlobal
                         .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
-        assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
-                .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
+                mDisplayManagerGlobal
+                        .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+                                0));
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                 mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
                         DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java b/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
deleted file mode 100644
index b4f1dee..0000000
--- a/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
+++ /dev/null
@@ -1,51 +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 android.hardware.input;
-
-import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link com.android.hardware.input.Flags}
- *
- * Build/Install/Run:
- *  atest FrameworksCoreTests:InputFlagsTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class InputFlagsTest {
-
-    /**
-     * Test that the flags work
-     */
-    @Test
-    public void testFlags() {
-        // No crash when accessing the flag.
-        keyboardLayoutPreviewFlag();
-        keyboardA11yStickyKeysFlag();
-    }
-}
-
diff --git a/core/tests/coretests/src/android/hardware/input/OWNERS b/core/tests/coretests/src/android/hardware/input/OWNERS
deleted file mode 100644
index 3f8a602..0000000
--- a/core/tests/coretests/src/android/hardware/input/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-include /core/java/android/hardware/input/OWNERS
-
diff --git a/core/tests/coretests/src/android/view/WindowManagerTests.java b/core/tests/coretests/src/android/view/WindowManagerTests.java
index 211d768..b32aa46 100644
--- a/core/tests/coretests/src/android/view/WindowManagerTests.java
+++ b/core/tests/coretests/src/android/view/WindowManagerTests.java
@@ -16,9 +16,6 @@
 
 package android.view;
 
-import static com.android.window.flags.Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG;
-
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -47,19 +44,8 @@
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
-    public void testHasWindowExtensionsEnabled_flagDisabled() {
-        mSetFlagsRule.disableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);
-
-        // Before FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, Extensions are always bundled with AE.
-        assertEquals(isActivityEmbeddingEnableForAll(),
-                WindowManager.hasWindowExtensionsEnabled());
-    }
-
-    @Test
-    public void testHasWindowExtensionsEnabled_flagEnabled() {
-        mSetFlagsRule.enableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);
-
-        // Extensions should be enabled on all devices.
+    public void testHasWindowExtensionsEnabled() {
+        // Extensions should be enabled on all phones/tablets.
         assertTrue(WindowManager.hasWindowExtensionsEnabled());
     }
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3b0eab4..cc5c6af 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 47;
+    private static final int NUM_MARSHALLED_PROPERTIES = 48;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index b42bcee..5f89f9c 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -36,11 +36,15 @@
 import android.graphics.Insets;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.autofill.AutofillId;
+import android.view.contentcapture.flags.Flags;
 import android.view.contentprotection.ContentProtectionEventProcessor;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -90,6 +94,8 @@
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock private IContentCaptureManager mMockSystemServerInterface;
 
     @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
@@ -407,6 +413,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_FLUSH_AFTER_EACH_FRAME)
     @SuppressWarnings("GuardedBy")
     public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled()
             throws RemoteException {
@@ -434,6 +441,34 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLUSH_AFTER_EACH_FRAME)
+    @SuppressWarnings("GuardedBy")
+    public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled_Flush()
+            throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSession session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        // Override the processor for interaction verification.
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+        notifyContentCaptureEvents(session);
+        mTestableLooper.processAllMessages();
+
+        // Force flush will happen twice.
+        verify(mMockContentCaptureDirectManager, times(1))
+                .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any());
+        verify(mMockContentCaptureDirectManager, times(1))
+                .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any());
+        // 5 view events + 2 view tree events + 1 flush event
+        verify(mMockContentProtectionEventProcessor, times(8)).processEvent(any());
+        assertThat(session.mEvents).isEmpty();
+    }
+
+    @Test
     public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
         ContentCaptureOptions options =
                 createOptions(
diff --git a/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt b/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt
new file mode 100644
index 0000000..e8e8a2e
--- /dev/null
+++ b/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 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.window
+
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.content.ContextWrapper
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.platform.test.annotations.Presubmit
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test to verify [ConfigurationDispatcher]
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ConfigurationDispatcherTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(Parameterized::class)
+class ConfigurationDispatcherTest(private val shouldReportPrivateChanges: Boolean) {
+
+    /**
+     * Verifies [ConfigurationDispatcher.shouldReportPrivateChanges].
+     */
+    @Test
+    fun testConfigurationDispatcher() {
+        val receiver = TestConfigurationReceiver(shouldReportPrivateChanges)
+        val config = Configuration().apply {
+            orientation = ORIENTATION_PORTRAIT
+        }
+
+        // Verify public config field change
+        receiver.windowToken.onConfigurationChangedInner(receiver, config, DEFAULT_DISPLAY, true)
+
+        assertThat(receiver.receivedConfig).isEqualTo(config)
+
+        // Clear the config value
+        receiver.receivedConfig.unset()
+
+        // Verify private config field change
+        config.windowConfiguration.windowingMode = WINDOWING_MODE_MULTI_WINDOW
+
+        receiver.windowToken.onConfigurationChangedInner(receiver, config, DEFAULT_DISPLAY, true)
+
+        assertThat(receiver.receivedConfig).isEqualTo(
+            if (shouldReportPrivateChanges) {
+                config
+            } else {
+                Configuration.EMPTY
+            }
+        )
+    }
+
+    /**
+     * Test [android.content.Context] to implement [ConfigurationDispatcher] for testing.
+     *
+     * @param shouldReportPrivateChanges used to override
+     * [ConfigurationDispatcher.shouldReportPrivateChanges] for testing,
+     */
+    private class TestConfigurationReceiver(
+        private val shouldReportPrivateChanges: Boolean
+    ) : ContextWrapper(null), ConfigurationDispatcher {
+        val windowToken = WindowTokenClient()
+        val receivedConfig = Configuration()
+
+        init {
+            windowToken.attachContext(this)
+        }
+
+        override fun dispatchConfigurationChanged(configuration: Configuration) {
+            receivedConfig.setTo(configuration)
+        }
+
+        override fun shouldReportPrivateChanges(): Boolean {
+            return shouldReportPrivateChanges
+        }
+
+        override fun getDisplayId(): Int {
+            return DEFAULT_DISPLAY
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "shouldReportPrivateChange={0}")
+        @JvmStatic
+        fun data(): Collection<Any> {
+            return listOf(true, false)
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index 84ff40f..116dc12 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -266,4 +266,25 @@
 
         verify(mWindowTokenClient).onWindowTokenRemoved();
     }
+
+    @Test
+    public void testOnWindowConfigurationChanged_propagatedToCorrectToken() throws RemoteException {
+        doReturn(mWindowContextInfo).when(mWindowManagerService)
+                .attachWindowContextToDisplayContent(any(), any(), anyInt());
+
+        mController.onWindowConfigurationChanged(mWindowTokenClient, mConfiguration,
+                DEFAULT_DISPLAY + 1);
+
+        // Not propagated before attaching
+        verify(mWindowTokenClient, never()).onConfigurationChanged(mConfiguration,
+                DEFAULT_DISPLAY + 1);
+
+        assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
+
+        mController.onWindowConfigurationChanged(mWindowTokenClient, mConfiguration,
+                DEFAULT_DISPLAY + 1);
+
+        // Now that's attached, propagating it.
+        verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
index 52ff79d..2edab62 100644
--- a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
@@ -22,6 +22,8 @@
 
 import android.os.SystemClock;
 
+import android.os.SystemClock;
+
 import androidx.test.runner.AndroidJUnit4;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -54,13 +56,13 @@
      */
     @Test
     public void testTtl_Zero() {
-        TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(0);
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(0);
 
         int first = s.get(mFetcher);
         assertEquals(first, 0);
         int second = s.get(mFetcher);
         assertEquals(second, 1);
-        s.advanceTime(20);
+        SystemClock.sleep(20);
         int third = s.get(mFetcher);
         assertEquals(third, 2);
     }
@@ -71,14 +73,14 @@
      */
     @Test
     public void testTtl_100() {
-        TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(100);
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
 
         int first = s.get(mFetcher);
         assertEquals(first, 0);
         int second = s.get(mFetcher);
         // Too early to change
         assertEquals(second, 0);
-        s.advanceTime(150);
+        SystemClock.sleep(150);
         int third = s.get(mFetcher);
         // Changed by now
         assertEquals(third, 1);
@@ -93,11 +95,11 @@
      */
     @Test
     public void testTtl_Negative() {
-        TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(-1);
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(-1);
 
         int first = s.get(mFetcher);
         assertEquals(first, 0);
-        s.advanceTime(200);
+        SystemClock.sleep(200);
         // Should return the original value every time
         int second = s.get(mFetcher);
         assertEquals(second, 0);
@@ -109,7 +111,7 @@
      */
     @Test
     public void testTtl_Spam() {
-        TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(100);
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
         assertCount(s, 1000, 7, 15);
     }
 
@@ -119,13 +121,28 @@
      */
     @Test
     public void testRate_10hz() {
-        TestRateLimitingCache<Integer> s = new TestRateLimitingCache<>(1000, 10);
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10);
         // At 10 per second, 2 seconds should not exceed about 30, assuming overlap into left and
         // right windows that allow 10 each
         assertCount(s, 2000, 20, 33);
     }
 
     /**
+     * Test that using a different timebase works correctly.
+     */
+    @Test
+    public void testTimebase() {
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(1000, 10) {
+            @Override
+            protected long getTime() {
+                return SystemClock.elapsedRealtime() / 2;
+            }
+        };
+        // Timebase is moving at half the speed, so only allows for 1 second worth in 2 seconds.
+        assertCount(s, 2000, 10, 22);
+    }
+
+    /**
      * Exercises concurrent access to the cache.
      */
     @Test
@@ -270,36 +287,15 @@
      * @param minCount the lower end of the expected number of fetches, with a margin for error
      * @param maxCount the higher end of the expected number of fetches, with a margin for error
      */
-    private void assertCount(TestRateLimitingCache<Integer> cache, long period,
+    private void assertCount(RateLimitingCache<Integer> cache, long period,
             int minCount, int maxCount) {
-        long startTime = cache.getTime();
-        while (cache.getTime() < startTime + period) {
+        long startTime = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() < startTime + period) {
             int value = cache.get(mFetcher);
-            cache.advanceTime(5);
+            SystemClock.sleep(5);
         }
         int latest = cache.get(mFetcher);
         assertTrue("Latest should be between " + minCount + " and " + maxCount
                         + " but is " + latest, latest <= maxCount && latest >= minCount);
     }
-
-    private static class TestRateLimitingCache<Value> extends RateLimitingCache<Value> {
-        private long mTime;
-
-        public TestRateLimitingCache(long periodMillis) {
-            super(periodMillis);
-        }
-
-        public TestRateLimitingCache(long periodMillis, int count) {
-            super(periodMillis, count);
-        }
-
-        public void advanceTime(long time) {
-            mTime += time;
-        }
-
-        @Override
-        public long getTime() {
-            return mTime;
-        }
-    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9baa31f..282886a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -121,18 +121,20 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 300, Color.RED)));
+                List.of(new DrawableSegment(10, 310, Color.RED)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -141,14 +143,14 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedRed = 0x80FF0000;
         expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 300, fadedRed, true)));
+                List.of(new DrawableSegment(10, 310, fadedRed, true)));
 
-        assertThat(p.second).isEqualTo(0);
+        assertThat(p.second).isEqualTo(10);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -168,18 +170,20 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 300, Color.RED)));
+                List.of(new DrawableSegment(10, 310, Color.RED)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -188,9 +192,9 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
-        assertThat(p.second).isEqualTo(300);
+        assertThat(p.second).isEqualTo(310);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -219,6 +223,42 @@
                 progressMax);
     }
 
+    @Test
+    public void processAndConvertToParts_pointPositionIsZero() {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(0).setColor(Color.RED));
+        int progress = 50;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
+
+        // Point at the start is dropped.
+        List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+    }
+
+    @Test
+    public void processAndConvertToParts_pointPositionAtMax() {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(100).setColor(Color.RED));
+        int progress = 50;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
+
+        // Point at the end is dropped.
+        List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void processAndConvertToParts_pointPositionAboveMax() {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
@@ -249,18 +289,20 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 300, Color.BLUE)));
+                List.of(new DrawableSegment(10, 310, Color.BLUE)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -269,15 +311,15 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedBlue = 0x800000FF;
         expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 180, Color.BLUE),
-                        new DrawableSegment(180, 300, fadedBlue, true)));
+                List.of(new DrawableSegment(10, 190, Color.BLUE),
+                        new DrawableSegment(190, 310, fadedBlue, true)));
 
-        assertThat(p.second).isEqualTo(180);
+        assertThat(p.second).isEqualTo(190);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -299,19 +341,21 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 146, Color.RED),
-                        new DrawableSegment(150, 300, Color.GREEN)));
+                List.of(new DrawableSegment(10, 156, Color.RED),
+                        new DrawableSegment(160, 310, Color.GREEN)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -319,15 +363,15 @@
         boolean isStyledByProgress = true;
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedGreen = 0x8000FF00;
-        expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(0, 146, Color.RED),
-                new DrawableSegment(150, 180, Color.GREEN),
-                new DrawableSegment(180, 300, fadedGreen, true)));
+        expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(10, 156, Color.RED),
+                new DrawableSegment(160, 190, Color.GREEN),
+                new DrawableSegment(190, 310, fadedGreen, true)));
 
-        assertThat(p.second).isEqualTo(180);
+        assertThat(p.second).isEqualTo(190);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -353,10 +397,12 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = false;
+        int trackerDrawWidth = 0;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 146, Color.RED),
@@ -368,7 +414,7 @@
         boolean isStyledByProgress = true;
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedGreen = 0x8000FF00;
@@ -409,26 +455,28 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 35, Color.BLUE),
-                        new DrawablePoint(39, 51, Color.RED),
-                        new DrawableSegment(55, 65, Color.BLUE),
-                        new DrawablePoint(69, 81, Color.BLUE),
-                        new DrawableSegment(85, 170, Color.BLUE),
-                        new DrawablePoint(174, 186, Color.BLUE),
-                        new DrawableSegment(190, 215, Color.BLUE),
-                        new DrawablePoint(219, 231, Color.YELLOW),
-                        new DrawableSegment(235, 300, Color.BLUE)));
+                List.of(new DrawableSegment(10, 45, Color.BLUE),
+                        new DrawablePoint(49, 61, Color.RED),
+                        new DrawableSegment(65, 75, Color.BLUE),
+                        new DrawablePoint(79, 91, Color.BLUE),
+                        new DrawableSegment(95, 180, Color.BLUE),
+                        new DrawablePoint(184, 196, Color.BLUE),
+                        new DrawableSegment(200, 225, Color.BLUE),
+                        new DrawablePoint(229, 241, Color.YELLOW),
+                        new DrawableSegment(245, 310, Color.BLUE)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -437,23 +485,23 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedBlue = 0x800000FF;
         int fadedYellow = 0x80FFFF00;
         expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
-                        new DrawablePoint(38.219177F, 50.219177F, Color.RED),
-                        new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
-                        new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
-                        new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
-                        new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
-                        new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
-                        new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
-                        new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+                List.of(new DrawableSegment(10, 44.219177F, Color.BLUE),
+                        new DrawablePoint(48.219177F, 60.219177F, Color.RED),
+                        new DrawableSegment(64.219177F, 80.21918F, Color.BLUE),
+                        new DrawablePoint(84.21918F, 96.21918F, Color.BLUE),
+                        new DrawableSegment(100.21918F, 182.38356F, Color.BLUE),
+                        new DrawablePoint(186.38356F, 198.38356F, Color.BLUE),
+                        new DrawableSegment(202.38356F, 227.0137F, fadedBlue, true),
+                        new DrawablePoint(231.0137F, 243.0137F, fadedYellow),
+                        new DrawableSegment(247.0137F, 310F, fadedBlue, true)));
 
-        assertThat(p.second).isEqualTo(182.38356F);
+        assertThat(p.second).isEqualTo(192.38356F);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -488,25 +536,29 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
+
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
-                        new DrawableSegment(55, 65, Color.RED),
-                        new DrawablePoint(69, 81, Color.BLUE),
-                        new DrawableSegment(85, 146, Color.RED),
-                        new DrawableSegment(150, 170, Color.GREEN),
-                        new DrawablePoint(174, 186, Color.BLUE),
-                        new DrawableSegment(190, 215, Color.GREEN),
-                        new DrawablePoint(219, 231, Color.YELLOW),
-                        new DrawableSegment(235, 300, Color.GREEN)));
+                List.of(new DrawableSegment(10, 45, Color.RED),
+                        new DrawablePoint(49, 61, Color.RED),
+                        new DrawableSegment(65, 75, Color.RED),
+                        new DrawablePoint(79, 91, Color.BLUE),
+                        new DrawableSegment(95, 156, Color.RED),
+                        new DrawableSegment(160, 180, Color.GREEN),
+                        new DrawablePoint(184, 196, Color.BLUE),
+                        new DrawableSegment(200, 225, Color.GREEN),
+                        new DrawablePoint(229, 241, Color.YELLOW),
+                        new DrawableSegment(245, 310, Color.GREEN)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -515,99 +567,24 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedGreen = 0x8000FF00;
         int fadedYellow = 0x80FFFF00;
         expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 34.095238F, Color.RED),
-                        new DrawablePoint(38.095238F, 50.095238F, Color.RED),
-                        new DrawableSegment(54.095238F, 70.09524F, Color.RED),
-                        new DrawablePoint(74.09524F, 86.09524F, Color.BLUE),
-                        new DrawableSegment(90.09524F, 148.9524F, Color.RED),
-                        new DrawableSegment(152.95238F, 172.7619F, Color.GREEN),
-                        new DrawablePoint(176.7619F, 188.7619F, Color.BLUE),
-                        new DrawableSegment(192.7619F, 217.33333F, fadedGreen, true),
-                        new DrawablePoint(221.33333F, 233.33333F, fadedYellow),
-                        new DrawableSegment(237.33333F, 299.99997F, fadedGreen, true)));
+                List.of(new DrawableSegment(10, 44.095238F, Color.RED),
+                        new DrawablePoint(48.095238F, 60.095238F, Color.RED),
+                        new DrawableSegment(64.095238F, 80.09524F, Color.RED),
+                        new DrawablePoint(84.09524F, 96.09524F, Color.BLUE),
+                        new DrawableSegment(100.09524F, 158.9524F, Color.RED),
+                        new DrawableSegment(162.95238F, 182.7619F, Color.GREEN),
+                        new DrawablePoint(186.7619F, 198.7619F, Color.BLUE),
+                        new DrawableSegment(202.7619F, 227.33333F, fadedGreen, true),
+                        new DrawablePoint(231.33333F, 243.33333F, fadedYellow),
+                        new DrawableSegment(247.33333F, 309.99997F, fadedGreen, true)));
 
-        assertThat(p.second).isEqualTo(182.7619F);
-        assertThat(p.first).isEqualTo(expectedDrawableParts);
-    }
-
-    @Test
-    public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
-            throws NotEnoughWidthToFitAllPartsException {
-        List<ProgressStyle.Segment> segments = new ArrayList<>();
-        segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
-        segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
-        List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(0).setColor(Color.RED));
-        points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
-        points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
-        points.add(new ProgressStyle.Point(100).setColor(Color.YELLOW));
-        int progress = 60;
-        int progressMax = 100;
-
-        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
-                points, progress, progressMax);
-
-        List<Part> expectedParts = new ArrayList<>(
-                List.of(new Point(Color.RED),
-                        new Segment(0.25f, Color.RED),
-                        new Point(Color.BLUE),
-                        new Segment(0.25f, Color.RED),
-                        new Segment(0.10f, Color.GREEN),
-                        new Point(Color.BLUE),
-                        new Segment(0.4f, Color.GREEN),
-                        new Point(Color.YELLOW)));
-
-        assertThat(parts).isEqualTo(expectedParts);
-
-        float drawableWidth = 300;
-        float segSegGap = 4;
-        float segPointGap = 4;
-        float pointRadius = 6;
-        boolean hasTrackerIcon = true;
-
-        List<DrawablePart> drawableParts =
-                NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
-
-        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawablePoint(0, 12, Color.RED),
-                        new DrawableSegment(16, 65, Color.RED),
-                        new DrawablePoint(69, 81, Color.BLUE),
-                        new DrawableSegment(85, 146, Color.RED),
-                        new DrawableSegment(150, 170, Color.GREEN),
-                        new DrawablePoint(174, 186, Color.BLUE),
-                        new DrawableSegment(190, 284, Color.GREEN),
-                        new DrawablePoint(288, 300, Color.YELLOW)));
-
-        assertThat(drawableParts).isEqualTo(expectedDrawableParts);
-
-        float segmentMinWidth = 16;
-        boolean isStyledByProgress = true;
-
-        Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
-                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
-
-        // Colors with 50% opacity
-        int fadedGreen = 0x8000FF00;
-        int fadedYellow = 0x80FFFF00;
-        expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawablePoint(0, 12, Color.RED),
-                        new DrawableSegment(16, 65, Color.RED),
-                        new DrawablePoint(69, 81, Color.BLUE),
-                        new DrawableSegment(85, 146, Color.RED),
-                        new DrawableSegment(150, 170, Color.GREEN),
-                        new DrawablePoint(174, 186, Color.BLUE),
-                        new DrawableSegment(190, 284, fadedGreen, true),
-                        new DrawablePoint(288, 300, fadedYellow)));
-
-        assertThat(p.second).isEqualTo(180);
+        assertThat(p.second).isEqualTo(192.7619F);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -644,27 +621,29 @@
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, -7, Color.RED),
-                        new DrawablePoint(-3, 9, Color.RED),
-                        new DrawableSegment(13, 65, Color.RED),
-                        new DrawablePoint(69, 81, Color.BLUE),
-                        new DrawableSegment(85, 146, Color.RED),
-                        new DrawableSegment(150, 170, Color.GREEN),
-                        new DrawablePoint(174, 186, Color.BLUE),
-                        new DrawableSegment(190, 287, Color.GREEN),
-                        new DrawablePoint(291, 303, Color.YELLOW),
-                        new DrawableSegment(307, 300, Color.GREEN)));
+                List.of(new DrawableSegment(10, 3, Color.RED),
+                        new DrawablePoint(7, 19, Color.RED),
+                        new DrawableSegment(23, 75, Color.RED),
+                        new DrawablePoint(79, 91, Color.BLUE),
+                        new DrawableSegment(95, 156, Color.RED),
+                        new DrawableSegment(160, 180, Color.GREEN),
+                        new DrawablePoint(184, 196, Color.BLUE),
+                        new DrawableSegment(200, 297, Color.GREEN),
+                        new DrawablePoint(301, 313, Color.YELLOW),
+                        new DrawableSegment(317, 310, Color.GREEN)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -673,24 +652,24 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         // Colors with 50% opacity
         int fadedGreen = 0x8000FF00;
         int fadedYellow = 0x80FFFF00;
         expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 16, Color.RED),
-                        new DrawablePoint(20, 32, Color.RED),
-                        new DrawableSegment(36, 78.02409F, Color.RED),
-                        new DrawablePoint(82.02409F, 94.02409F, Color.BLUE),
-                        new DrawableSegment(98.02409F, 146.55421F, Color.RED),
-                        new DrawableSegment(150.55421F, 169.44579F, Color.GREEN),
-                        new DrawablePoint(173.44579F, 185.44579F, Color.BLUE),
-                        new DrawableSegment(189.44579F, 264, fadedGreen, true),
-                        new DrawablePoint(268, 280, fadedYellow),
-                        new DrawableSegment(284, 300, fadedGreen, true)));
+                List.of(new DrawableSegment(10, 26, Color.RED),
+                        new DrawablePoint(30, 42, Color.RED),
+                        new DrawableSegment(46, 88.02409F, Color.RED),
+                        new DrawablePoint(92.02409F, 104.02409F, Color.BLUE),
+                        new DrawableSegment(108.02409F, 156.55421F, Color.RED),
+                        new DrawableSegment(160.55421F, 179.44579F, Color.GREEN),
+                        new DrawablePoint(183.44579F, 195.44579F, Color.BLUE),
+                        new DrawableSegment(199.44579F, 274, fadedGreen, true),
+                        new DrawablePoint(278, 290, fadedYellow),
+                        new DrawableSegment(294, 310, fadedGreen, true)));
 
-        assertThat(p.second).isEqualTo(179.44579F);
+        assertThat(p.second).isEqualTo(189.44579F);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -711,31 +690,38 @@
                 points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
-                        new Segment(0.10f, Color.RED), new Point(Color.BLUE),
-                        new Segment(0.25f, Color.RED), new Segment(0.25f, Color.GREEN),
-                        new Point(Color.YELLOW), new Segment(0.25f, Color.GREEN)));
+                List.of(new Segment(0.15f, Color.RED),
+                        new Point(Color.RED),
+                        new Segment(0.10f, Color.RED),
+                        new Point(Color.BLUE),
+                        new Segment(0.25f, Color.RED),
+                        new Segment(0.25f, Color.GREEN),
+                        new Point(Color.YELLOW),
+                        new Segment(0.25f, Color.GREEN)));
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
-                        new DrawableSegment(55, 65, Color.RED),
-                        new DrawablePoint(69, 81, Color.BLUE),
-                        new DrawableSegment(85, 146, Color.RED),
-                        new DrawableSegment(150, 215, Color.GREEN),
-                        new DrawablePoint(219, 231, Color.YELLOW),
-                        new DrawableSegment(235, 300, Color.GREEN)));
+                List.of(new DrawableSegment(10, 45, Color.RED),
+                        new DrawablePoint(49, 61, Color.RED),
+                        new DrawableSegment(65, 75, Color.RED),
+                        new DrawablePoint(79, 91, Color.BLUE),
+                        new DrawableSegment(95, 156, Color.RED),
+                        new DrawableSegment(160, 225, Color.GREEN),
+                        new DrawablePoint(229, 241, Color.YELLOW),
+                        new DrawableSegment(245, 310, Color.GREEN)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -744,34 +730,34 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
         expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 34.296295F, Color.RED),
-                        new DrawablePoint(38.296295F, 50.296295F, Color.RED),
-                        new DrawableSegment(54.296295F, 70.296295F, Color.RED),
-                        new DrawablePoint(74.296295F, 86.296295F, Color.BLUE),
-                        new DrawableSegment(90.296295F, 149.62962F, Color.RED),
-                        new DrawableSegment(153.62962F, 216.8148F, Color.GREEN),
-                        new DrawablePoint(220.81482F, 232.81482F, Color.YELLOW),
-                        new DrawableSegment(236.81482F, 300, Color.GREEN)));
+                List.of(new DrawableSegment(10, 44.296295F, Color.RED),
+                        new DrawablePoint(48.296295F, 60.296295F, Color.RED),
+                        new DrawableSegment(64.296295F, 80.296295F, Color.RED),
+                        new DrawablePoint(84.296295F, 96.296295F, Color.BLUE),
+                        new DrawableSegment(100.296295F, 159.62962F, Color.RED),
+                        new DrawableSegment(163.62962F, 226.8148F, Color.GREEN),
+                        new DrawablePoint(230.81482F, 242.81482F, Color.YELLOW),
+                        new DrawableSegment(246.81482F, 310, Color.GREEN)));
 
-        assertThat(p.second).isEqualTo(182.9037F);
+        assertThat(p.second).isEqualTo(192.9037F);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
-    // The only difference from the `zeroWidthDrawableSegment` test below is the longer
+    // The only difference from the `segmentWidthAtMin` test below is the longer
     // segmentMinWidth (= 16dp).
     @Test
-    public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+    public void maybeStretchAndRescaleSegments_segmentWidthBelowMin()
             throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
-        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
         List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(200).setColor(Color.BLUE));
         int progress = 1000;
         int progressMax = 1000;
 
@@ -779,28 +765,32 @@
                 points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
-                        new Segment(0.2f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+                List.of(new Segment(0.2f, Color.BLUE),
+                        new Point(Color.BLUE),
+                        new Segment(0.1f, Color.BLUE),
+                        new Segment(0.3f, Color.BLUE),
                         new Segment(0.4f, Color.BLUE)));
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 200;
+        float drawableWidth = 220;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawablePoint(0, 12, Color.BLUE),
-                        new DrawableSegment(16, 16, Color.BLUE),
-                        new DrawableSegment(20, 56, Color.BLUE),
-                        new DrawableSegment(60, 116, Color.BLUE),
-                        new DrawableSegment(120, 200, Color.BLUE)));
+                List.of(new DrawableSegment(10, 40, Color.BLUE),
+                        new DrawablePoint(44, 56, Color.BLUE),
+                        new DrawableSegment(60, 66, Color.BLUE),
+                        new DrawableSegment(70, 126, Color.BLUE),
+                        new DrawableSegment(130, 210, Color.BLUE)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -809,30 +799,31 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
-        expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
-                new DrawableSegment(16, 32, Color.BLUE),
-                new DrawableSegment(36, 69.41936F, Color.BLUE),
-                new DrawableSegment(73.41936F, 124.25807F, Color.BLUE),
-                new DrawableSegment(128.25807F, 200, Color.BLUE)));
+        expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(10, 38.81356F, Color.BLUE),
+                        new DrawablePoint(42.81356F, 54.81356F, Color.BLUE),
+                        new DrawableSegment(58.81356F, 74.81356F, Color.BLUE),
+                        new DrawableSegment(78.81356F, 131.42374F, Color.BLUE),
+                        new DrawableSegment(135.42374F, 210, Color.BLUE)));
 
-        assertThat(p.second).isEqualTo(200);
+        assertThat(p.second).isEqualTo(210);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
-    // The only difference from the `negativeWidthDrawableSegment` test above is the shorter
+    // The only difference from the `segmentWidthBelowMin` test above is the shorter
     // segmentMinWidth (= 10dp).
     @Test
-    public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+    public void maybeStretchAndRescaleSegments_segmentWidthAtMin()
             throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
-        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
         List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(200).setColor(Color.BLUE));
         int progress = 1000;
         int progressMax = 1000;
 
@@ -840,28 +831,32 @@
                 points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
-                        new Segment(0.2f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+                List.of(new Segment(0.2f, Color.BLUE),
+                        new Point(Color.BLUE),
+                        new Segment(0.1f, Color.BLUE),
+                        new Segment(0.3f, Color.BLUE),
                         new Segment(0.4f, Color.BLUE)));
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 200;
+        float drawableWidth = 220;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawablePoint(0, 12, Color.BLUE),
-                        new DrawableSegment(16, 16, Color.BLUE),
-                        new DrawableSegment(20, 56, Color.BLUE),
-                        new DrawableSegment(60, 116, Color.BLUE),
-                        new DrawableSegment(120, 200, Color.BLUE)));
+                List.of(new DrawableSegment(10, 40, Color.BLUE),
+                        new DrawablePoint(44, 56, Color.BLUE),
+                        new DrawableSegment(60, 66, Color.BLUE),
+                        new DrawableSegment(70, 126, Color.BLUE),
+                        new DrawableSegment(130, 210, Color.BLUE)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -870,15 +865,16 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
-        expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
-                new DrawableSegment(16, 26, Color.BLUE),
-                new DrawableSegment(30, 64.169014F, Color.BLUE),
-                new DrawableSegment(68.169014F, 120.92958F, Color.BLUE),
-                new DrawableSegment(124.92958F, 200, Color.BLUE)));
+        expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(10, 39.411766F, Color.BLUE),
+                        new DrawablePoint(43.411766F, 55.411766F, Color.BLUE),
+                        new DrawableSegment(59.411766F, 69.411766F, Color.BLUE),
+                        new DrawableSegment(73.411766F, 128.05884F, Color.BLUE),
+                        new DrawableSegment(132.05882F, 210, Color.BLUE)));
 
-        assertThat(p.second).isEqualTo(200);
+        assertThat(p.second).isEqualTo(210);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -886,12 +882,12 @@
     public void maybeStretchAndRescaleSegments_noStretchingNecessary()
             throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
-        segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
         List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(100).setColor(Color.BLUE));
         int progress = 1000;
         int progressMax = 1000;
 
@@ -899,28 +895,32 @@
                 points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
-                        new Segment(0.1f, Color.BLUE), new Segment(0.3f, Color.BLUE),
+                List.of(new Segment(0.1f, Color.BLUE),
+                        new Point(Color.BLUE),
+                        new Segment(0.2f, Color.BLUE),
+                        new Segment(0.3f, Color.BLUE),
                         new Segment(0.4f, Color.BLUE)));
 
         assertThat(parts).isEqualTo(expectedParts);
 
-        float drawableWidth = 200;
+        float drawableWidth = 220;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawablePoint(0, 12, Color.BLUE),
-                        new DrawableSegment(16, 36, Color.BLUE),
-                        new DrawableSegment(40, 56, Color.BLUE),
-                        new DrawableSegment(60, 116, Color.BLUE),
-                        new DrawableSegment(120, 200, Color.BLUE)));
+                List.of(new DrawableSegment(10, 20, Color.BLUE),
+                        new DrawablePoint(24, 36, Color.BLUE),
+                        new DrawableSegment(40, 66, Color.BLUE),
+                        new DrawableSegment(70, 126, Color.BLUE),
+                        new DrawableSegment(130, 210, Color.BLUE)));
 
         assertThat(drawableParts).isEqualTo(expectedDrawableParts);
 
@@ -929,9 +929,9 @@
 
         Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
 
-        assertThat(p.second).isEqualTo(200);
+        assertThat(p.second).isEqualTo(210);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -951,10 +951,10 @@
         segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
         segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(0).setColor(orange));
         points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(10).setColor(orange));
         points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
-        points.add(new ProgressStyle.Point(100).setColor(orange));
+        points.add(new ProgressStyle.Point(90).setColor(orange));
         int progress = 50;
         int progressMax = 100;
 
@@ -962,10 +962,10 @@
                 points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Point(orange),
-                        new Segment(0.01f, orange),
+                List.of(new Segment(0.01f, orange),
                         new Point(Color.BLUE),
                         new Segment(0.09f, orange),
+                        new Point(orange),
                         new Segment(0.1f, Color.YELLOW),
                         new Segment(0.1f, Color.BLUE),
                         new Segment(0.1f, Color.GREEN),
@@ -976,21 +976,23 @@
                         new Segment(0.1f, Color.YELLOW),
                         new Segment(0.1f, Color.BLUE),
                         new Segment(0.1f, Color.GREEN),
-                        new Segment(0.1f, Color.RED),
-                        new Point(orange)));
+                        new Point(orange),
+                        new Segment(0.1f, Color.RED)));
 
         assertThat(parts).isEqualTo(expectedParts);
 
         // For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
-        float drawableWidth = 299;
+        float drawableWidth = 319;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         List<DrawablePart> drawableParts =
                 NotificationProgressBar.processPartsAndConvertToDrawableParts(
-                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon,
+                        trackerDrawWidth);
 
         // Skips the validation of the intermediate list of DrawableParts.
 
@@ -999,7 +1001,7 @@
 
         NotificationProgressBar.maybeStretchAndRescaleSegments(
                 parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
-                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+                isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
     }
 
     @Test
@@ -1015,11 +1017,12 @@
         int progress = 60;
         int progressMax = 100;
 
-        float drawableWidth = 300;
+        float drawableWidth = 320;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         float segmentMinWidth = 16;
         boolean isStyledByProgress = true;
@@ -1036,24 +1039,24 @@
                         pointRadius,
                         hasTrackerIcon,
                         segmentMinWidth,
-                        isStyledByProgress
-                );
+                        isStyledByProgress,
+                        trackerDrawWidth);
 
         // Colors with 50% opacity
         int fadedBlue = 0x800000FF;
         int fadedYellow = 0x80FFFF00;
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
-                        new DrawablePoint(38.219177F, 50.219177F, Color.RED),
-                        new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
-                        new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
-                        new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
-                        new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
-                        new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
-                        new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
-                        new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+                List.of(new DrawableSegment(10, 44.219177F, Color.BLUE),
+                        new DrawablePoint(48.219177F, 60.219177F, Color.RED),
+                        new DrawableSegment(64.219177F, 80.21918F, Color.BLUE),
+                        new DrawablePoint(84.21918F, 96.21918F, Color.BLUE),
+                        new DrawableSegment(100.21918F, 182.38356F, Color.BLUE),
+                        new DrawablePoint(186.38356F, 198.38356F, Color.BLUE),
+                        new DrawableSegment(202.38356F, 227.0137F, fadedBlue, true),
+                        new DrawablePoint(231.0137F, 243.0137F, fadedYellow),
+                        new DrawableSegment(247.0137F, 310F, fadedBlue, true)));
 
-        assertThat(p.second).isEqualTo(182.38356F);
+        assertThat(p.second).isEqualTo(192.38356F);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 
@@ -1065,11 +1068,12 @@
         int progress = 60;
         int progressMax = 100;
 
-        float drawableWidth = 100;
+        float drawableWidth = 120;
         float segSegGap = 4;
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
+        int trackerDrawWidth = 20;
 
         float segmentMinWidth = 16;
         boolean isStyledByProgress = true;
@@ -1086,16 +1090,16 @@
                         pointRadius,
                         hasTrackerIcon,
                         segmentMinWidth,
-                        isStyledByProgress
-                );
+                        isStyledByProgress,
+                        trackerDrawWidth);
 
-        // Colors with 50%f opacity
+        // Colors with 50% opacity
         int fadedBlue = 0x800000FF;
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
-                List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
-                        new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+                List.of(new DrawableSegment(10, 70F, Color.BLUE),
+                        new DrawableSegment(70F, 110, fadedBlue, true)));
 
-        assertThat(p.second).isWithin(1e-5f).of(60);
+        assertThat(p.second).isEqualTo(70);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index e1f5b1c..140d268 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -90,7 +90,7 @@
                 new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
                 new Notification.ProgressStyle.Segment(50).setColor(Color.LTGRAY));
         final List<Notification.ProgressStyle.Point> points = List.of(
-                new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+                new Notification.ProgressStyle.Point(1).setColor(Color.RED),
                 new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
         final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
                 points,
@@ -121,7 +121,7 @@
                 new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
                 new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
         final List<Notification.ProgressStyle.Point> points = List.of(
-                new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+                new Notification.ProgressStyle.Point(1).setColor(Color.RED),
                 new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
         final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
                 points,
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 8f12828..7d236d2 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -95,7 +95,8 @@
 
     // Must be the last one
     // This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
-    private static final int FRAME_INFO_SIZE = FRAME_INTERVAL + 1;
+    // In calculating size, + 1 for Flags, and + 1 for WorkloadTarget from FrameInfo.h
+    private static final int FRAME_INFO_SIZE = FRAME_INTERVAL + 2;
 
     /** checkstyle */
     public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 13d0169..a08f88a 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -177,3 +177,10 @@
     description: "Factor task-view state tracking out of taskviewtransitions"
     bug: "384976265"
 }
+
+flag {
+    name: "enable_bubble_bar_on_phones"
+    namespace: "multitasking"
+    description: "Try out bubble bar on phones"
+    bug: "394869612"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index bce6c59..a32ec22 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -61,7 +61,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 import java.util.Optional
@@ -133,7 +132,7 @@
                 mainExecutor,
                 bgExecutor,
             )
-        bubbleController.asBubbles().setSysuiProxy(Mockito.mock(SysuiProxy::class.java))
+        bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>())
 
         shellInit.init()
 
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 88bfeb2..e865111 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -50,10 +50,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito
 import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
@@ -635,7 +635,7 @@
 
     @Test
     fun removeFromWindow_stopMonitoringSwipeUpGesture() {
-        bubbleStackView = Mockito.spy(bubbleStackView)
+        bubbleStackView = spy(bubbleStackView)
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             // No way to add to window in the test environment right now so just pretend
             bubbleStackView.onDetachedFromWindow()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
index af238d0..3499ee3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubjectTest.kt
@@ -29,7 +29,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
 /** Test for [UiEventSubject] */
@@ -130,10 +131,10 @@
     }
 
     private fun createBubble(appUid: Int, packageName: String, instanceId: InstanceId): Bubble {
-        return mock(Bubble::class.java).apply {
-            whenever(getAppUid()).thenReturn(appUid)
-            whenever(getPackageName()).thenReturn(packageName)
-            whenever(getInstanceId()).thenReturn(instanceId)
+        return mock<Bubble>() {
+            on { getAppUid() } doReturn appUid
+            on { getPackageName() } doReturn packageName
+            on { getInstanceId() } doReturn instanceId
         }
     }
 
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index c022a29..7b583137 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -73,7 +73,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
@@ -127,7 +126,7 @@
                 mainExecutor,
                 bgExecutor,
             )
-        bubbleController.asBubbles().setSysuiProxy(mock(SysuiProxy::class.java))
+        bubbleController.asBubbles().setSysuiProxy(mock<SysuiProxy>())
         // Flush so that proxy gets set
         mainExecutor.flushAll()
 
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index ce24275..05c1e09 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -13,20 +13,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="128dp"
-    android:height="4dp"
-    android:viewportWidth="128"
-    android:viewportHeight="4"
-    >
-    <group>
-        <clip-path
-            android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z"
-            />
-        <path
-            android:pathData="M0 0V4H128V0"
-            android:fillColor="@android:color/black"
-            />
-    </group>
-</vector>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@android:color/black"/>
+    <corners android:radius="2dp"/>
+    <size android:height="4dp"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
index 1d1cdfa..9451fd4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
@@ -20,7 +20,7 @@
     android:id="@+id/desktop_mode_caption"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center_horizontal">
+    android:gravity="center">
 
     <com.android.wm.shell.windowdecor.HandleImageButton
         android:id="@+id/caption_handle"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 50c0873..477d207 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -41,7 +41,7 @@
             android:id="@+id/application_icon"
             android:layout_width="@dimen/desktop_mode_caption_icon_radius"
             android:layout_height="@dimen/desktop_mode_caption_icon_radius"
-            android:layout_marginStart="12dp"
+            android:layout_marginStart="10dp"
             android:layout_marginEnd="12dp"
             android:contentDescription="@string/app_icon_text"
             android:importantForAccessibility="no"/>
@@ -53,10 +53,9 @@
 
         <com.android.wm.shell.windowdecor.HandleMenuImageButton
             android:id="@+id/collapse_menu_button"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:padding="4dp"
-            android:layout_marginEnd="14dp"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginEnd="16dp"
             android:layout_marginStart="14dp"
             android:contentDescription="@string/collapse_menu_text"
             android:src="@drawable/ic_baseline_expand_more_24"
@@ -78,40 +77,55 @@
 
         <ImageButton
             android:id="@+id/fullscreen_button"
-            android:layout_marginEnd="4dp"
+            android:paddingStart="16dp"
+            android:paddingEnd="12dp"
             android:contentDescription="@string/fullscreen_text"
             android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
             android:tint="@androidprv:color/materialColorOnSurface"
-            android:layout_weight="1"
             style="@style/DesktopModeHandleMenuWindowingButton"/>
 
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
         <ImageButton
             android:id="@+id/split_screen_button"
-            android:layout_marginStart="4dp"
-            android:layout_marginEnd="4dp"
+            android:paddingStart="14dp"
+            android:paddingEnd="14dp"
             android:contentDescription="@string/split_screen_text"
             android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
             android:tint="@androidprv:color/materialColorOnSurface"
-            android:layout_weight="1"
             style="@style/DesktopModeHandleMenuWindowingButton"/>
 
+        <Space
+            android:id="@+id/floating_button_space"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
         <ImageButton
             android:id="@+id/floating_button"
-            android:layout_marginStart="4dp"
-            android:layout_marginEnd="4dp"
+            android:paddingStart="14dp"
+            android:paddingEnd="14dp"
             android:contentDescription="@string/float_button_text"
             android:src="@drawable/desktop_mode_ic_handle_menu_floating"
             android:tint="@androidprv:color/materialColorOnSurface"
-            android:layout_weight="1"
             style="@style/DesktopModeHandleMenuWindowingButton"/>
 
+        <Space
+            android:id="@+id/desktop_button_space"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
         <ImageButton
             android:id="@+id/desktop_button"
-            android:layout_marginStart="4dp"
+            android:paddingStart="12dp"
+            android:paddingEnd="16dp"
             android:contentDescription="@string/desktop_text"
             android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
             android:tint="@androidprv:color/materialColorOnSurface"
-            android:layout_weight="1"
             style="@style/DesktopModeHandleMenuWindowingButton"/>
 
     </LinearLayout>
@@ -126,77 +140,33 @@
         android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:background="@drawable/desktop_mode_decor_handle_menu_background">
 
-        <LinearLayout
+        <com.android.wm.shell.windowdecor.HandleMenuActionButton
             android:id="@+id/screenshot_button"
             android:contentDescription="@string/screenshot_text"
-            style="@style/DesktopModeHandleMenuActionButtonLayout">
+            android:text="@string/screenshot_text"
+            android:src="@drawable/desktop_mode_ic_handle_menu_screenshot"
+            style="@style/DesktopModeHandleMenuActionButton"/>
 
-            <ImageView
-                android:id="@+id/image"
-                android:src="@drawable/desktop_mode_ic_handle_menu_screenshot"
-                android:importantForAccessibility="no"
-                style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
-            <com.android.wm.shell.windowdecor.MarqueedTextView
-                android:id="@+id/label"
-                android:text="@string/screenshot_text"
-                style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
-        </LinearLayout>
-
-        <LinearLayout
+        <com.android.wm.shell.windowdecor.HandleMenuActionButton
             android:id="@+id/new_window_button"
             android:contentDescription="@string/new_window_text"
-            style="@style/DesktopModeHandleMenuActionButtonLayout">
+            android:text="@string/new_window_text"
+            android:src="@drawable/desktop_mode_ic_handle_menu_new_window"
+            style="@style/DesktopModeHandleMenuActionButton"/>
 
-            <ImageView
-                android:id="@+id/image"
-                android:src="@drawable/desktop_mode_ic_handle_menu_new_window"
-                android:importantForAccessibility="no"
-                style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
-            <com.android.wm.shell.windowdecor.MarqueedTextView
-                android:id="@+id/label"
-                android:text="@string/new_window_text"
-                style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
-        </LinearLayout>
-
-        <LinearLayout
+        <com.android.wm.shell.windowdecor.HandleMenuActionButton
             android:id="@+id/manage_windows_button"
             android:contentDescription="@string/manage_windows_text"
-            style="@style/DesktopModeHandleMenuActionButtonLayout">
+            android:text="@string/manage_windows_text"
+            android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows"
+            style="@style/DesktopModeHandleMenuActionButton"/>
 
-            <ImageView
-                android:id="@+id/image"
-                android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows"
-                android:importantForAccessibility="no"
-                style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
-            <com.android.wm.shell.windowdecor.MarqueedTextView
-                android:id="@+id/label"
-                android:text="@string/manage_windows_text"
-                style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
-        </LinearLayout>
-
-        <LinearLayout
+        <com.android.wm.shell.windowdecor.HandleMenuActionButton
             android:id="@+id/change_aspect_ratio_button"
             android:contentDescription="@string/change_aspect_ratio_text"
-            style="@style/DesktopModeHandleMenuActionButtonLayout">
-
-            <ImageView
-                android:id="@+id/image"
-                android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
-                android:importantForAccessibility="no"
-                style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
-            <com.android.wm.shell.windowdecor.MarqueedTextView
-                android:id="@+id/label"
-                android:text="@string/change_aspect_ratio_text"
-                style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
-        </LinearLayout>
+            android:text="@string/change_aspect_ratio_text"
+            android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+            style="@style/DesktopModeHandleMenuActionButton"/>
     </LinearLayout>
 
     <LinearLayout
@@ -209,29 +179,14 @@
         android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:background="@drawable/desktop_mode_decor_handle_menu_background">
 
-        <LinearLayout
+        <com.android.wm.shell.windowdecor.HandleMenuActionButton
             android:id="@+id/open_in_app_or_browser_button"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:layout_marginEnd="8dp"
-            android:gravity="start|center_vertical"
-            android:paddingStart="16dp"
             android:contentDescription="@string/open_in_browser_text"
-            android:background="?android:selectableItemBackground">
-
-            <ImageView
-                android:id="@+id/image"
-                android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
-                android:importantForAccessibility="no"
-                style="@style/DesktopModeHandleMenuActionButtonImage"/>
-
-            <com.android.wm.shell.windowdecor.MarqueedTextView
-                android:id="@+id/label"
-                android:text="@string/open_in_browser_text"
-                style="@style/DesktopModeHandleMenuActionButtonTextView"/>
-
-        </LinearLayout>
+            android:text="@string/open_in_browser_text"
+            android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
+            style="@style/DesktopModeHandleMenuActionButton"
+            android:layout_width="0dp"
+            android:layout_weight="1"/>
 
         <ImageButton
             android:id="@+id/open_by_default_button"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
new file mode 100644
index 0000000..379f4e9
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2025 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:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/action_button"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="start|center_vertical"
+    android:paddingHorizontal="16dp"
+    android:clickable="true"
+    android:focusable="true"
+    android:orientation="horizontal"
+    android:background="?android:attr/selectableItemBackground">
+
+    <ImageView
+        android:id="@+id/image"
+        android:contentDescription="@+id/label"
+        style="@style/DesktopModeHandleMenuActionButtonImage"/>
+
+    <com.android.wm.shell.windowdecor.MarqueedTextView
+        android:id="@+id/label"
+        style="@style/DesktopModeHandleMenuActionButtonTextView"/>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
index fbb5caa..4ba0468 100644
--- a/libs/WindowManager/Shell/res/values/attrs.xml
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -23,4 +23,11 @@
     <declare-styleable name="MessageState">
         <attr name="state_task_focused" format="boolean"/>
     </declare-styleable>
+
+    <declare-styleable name="HandleMenuActionButton">
+        <attr name="android:text" format="string" />
+        <attr name="android:textColor" format="color" />
+        <attr name="android:src" format="reference" />
+        <attr name="android:drawableTint" format="color" />
+    </declare-styleable>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index e23d572..96e008e 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -570,7 +570,7 @@
     <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
 
     <!-- The radius of the caption menu icon. -->
-    <dimen name="desktop_mode_caption_icon_radius">24dp</dimen>
+    <dimen name="desktop_mode_caption_icon_radius">32dp</dimen>
 
     <!-- The radius of the caption menu shadow. -->
     <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a2231dd..1b7daa8 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -290,7 +290,7 @@
     <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
     <string name="fullscreen_text">Fullscreen</string>
     <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
-    <string name="desktop_text">Desktop Mode</string>
+    <string name="desktop_text">Desktop View</string>
     <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
     <string name="split_screen_text">Split Screen</string>
     <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
@@ -316,7 +316,7 @@
     <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
     <string name="collapse_menu_text">Close Menu</string>
     <!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] -->
-    <string name="desktop_mode_app_header_chip_text">Open Menu</string>
+    <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop View)</string>
     <!-- Maximize menu maximize button string. -->
     <string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
     <!-- Maximize menu snap buttons string. -->
@@ -342,10 +342,10 @@
     <!-- Accessibility text for the Maximize Menu's snap maximize/restore [CHAR LIMIT=NONE] -->
     <string name="desktop_mode_a11y_action_maximize_restore">Maximize or restore window size</string>
 
-    <!-- Accessibility action replacement for caption handle menu split screen button [CHAR LIMIT=NONE] -->
-    <string name="app_handle_menu_talkback_split_screen_mode_button_text">Enter split screen mode</string>
-    <!-- Accessibility action replacement for caption handle menu enter desktop mode button [CHAR LIMIT=NONE] -->
-    <string name="app_handle_menu_talkback_desktop_mode_button_text">Enter desktop windowing mode</string>
+    <!-- Accessibility action replacement for caption handle app chip buttons [CHAR LIMIT=NONE] -->
+    <string name="app_handle_chip_accessibility_announce">Open Menu</string>
+    <!-- Accessibility action replacement for caption handle menu buttons [CHAR LIMIT=NONE] -->
+    <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop View">%1$s</xliff:g></string>
     <!-- Accessibility action replacement for maximize menu enter snap left button [CHAR LIMIT=NONE] -->
     <string name="maximize_menu_talkback_action_snap_left_text">Resize window to left</string>
     <!-- Accessibility action replacement for maximize menu enter snap right button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 035004b..637b47a 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -40,13 +40,11 @@
         <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
     </style>
 
-    <style name="DesktopModeHandleMenuActionButtonLayout">
+    <style name="DesktopModeHandleMenuActionButton">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">52dp</item>
-        <item name="android:layout_weight">1</item>
-        <item name="android:gravity">start|center_vertical</item>
-        <item name="android:paddingHorizontal">16dp</item>
-        <item name="android:background">?android:selectableItemBackground</item>
+        <item name="android:textColor">@androidprv:color/materialColorOnSurface</item>
+        <item name="android:drawableTint">@androidprv:color/materialColorOnSurface</item>
     </style>
 
     <style name="DesktopModeHandleMenuActionButtonImage">
@@ -71,7 +69,6 @@
     <style name="DesktopModeHandleMenuWindowingButton">
         <item name="android:layout_width">48dp</item>
         <item name="android:layout_height">48dp</item>
-        <item name="android:padding">14dp</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:background">?android:selectableItemBackgroundBorderless</item>
     </style>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 0b1f76f..d280083 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -17,4 +17,23 @@
 <resources>
     <dimen name="floating_dismiss_icon_size">32dp</dimen>
     <dimen name="floating_dismiss_background_size">96dp</dimen>
+
+    <!-- Bubble drag zone dimensions -->
+    <dimen name="drag_zone_dismiss_fold">140dp</dimen>
+    <dimen name="drag_zone_dismiss_tablet">200dp</dimen>
+    <dimen name="drag_zone_bubble_fold">140dp</dimen>
+    <dimen name="drag_zone_bubble_tablet">200dp</dimen>
+    <dimen name="drag_zone_full_screen_width">512dp</dimen>
+    <dimen name="drag_zone_full_screen_height">44dp</dimen>
+    <dimen name="drag_zone_desktop_window_width">880dp</dimen>
+    <dimen name="drag_zone_desktop_window_height">300dp</dimen>
+    <dimen name="drag_zone_desktop_window_expanded_view_width">200dp</dimen>
+    <dimen name="drag_zone_desktop_window_expanded_view_height">350dp</dimen>
+    <dimen name="drag_zone_split_from_bubble_height">100dp</dimen>
+    <dimen name="drag_zone_split_from_bubble_width">60dp</dimen>
+    <dimen name="drag_zone_h_split_from_expanded_view_width">60dp</dimen>
+    <dimen name="drag_zone_v_split_from_expanded_view_width">200dp</dimen>
+    <dimen name="drag_zone_v_split_from_expanded_view_height_tablet">285dp</dimen>
+    <dimen name="drag_zone_v_split_from_expanded_view_height_fold_tall">150dp</dimen>
+    <dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index 35802c93..909e9d2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -16,11 +16,15 @@
 
 package com.android.wm.shell.shared.bubbles
 
+import android.content.Context
 import android.graphics.Rect
+import androidx.annotation.DimenRes
+import com.android.wm.shell.shared.R
 import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
 
 /** A class for creating drag zones for dragging bubble objects or dragging into bubbles. */
 class DragZoneFactory(
+    private val context: Context,
     private val deviceConfig: DeviceConfig,
     private val splitScreenModeChecker: SplitScreenModeChecker,
     private val desktopWindowModeChecker: DesktopWindowModeChecker,
@@ -29,23 +33,65 @@
     private val windowBounds: Rect
         get() = deviceConfig.windowBounds
 
-    // TODO b/393172431: move these to xml
-    private val dismissDragZoneSize = if (deviceConfig.isSmallTablet) 140 else 200
-    private val bubbleDragZoneTabletSize = 200
-    private val bubbleDragZoneFoldableSize = 140
-    private val fullScreenDragZoneWidth = 512
-    private val fullScreenDragZoneHeight = 44
-    private val desktopWindowDragZoneWidth = 880
-    private val desktopWindowDragZoneHeight = 300
-    private val desktopWindowFromExpandedViewDragZoneWidth = 200
-    private val desktopWindowFromExpandedViewDragZoneHeight = 350
-    private val splitFromBubbleDragZoneHeight = 100
-    private val splitFromBubbleDragZoneWidth = 60
-    private val hSplitFromExpandedViewDragZoneWidth = 60
-    private val vSplitFromExpandedViewDragZoneWidth = 200
-    private val vSplitFromExpandedViewDragZoneHeightTablet = 285
-    private val vSplitFromExpandedViewDragZoneHeightFoldTall = 150
-    private val vSplitFromExpandedViewDragZoneHeightFoldShort = 100
+    private var dismissDragZoneSize = 0
+    private var bubbleDragZoneTabletSize = 0
+    private var bubbleDragZoneFoldableSize = 0
+    private var fullScreenDragZoneWidth = 0
+    private var fullScreenDragZoneHeight = 0
+    private var desktopWindowDragZoneWidth = 0
+    private var desktopWindowDragZoneHeight = 0
+    private var desktopWindowFromExpandedViewDragZoneWidth = 0
+    private var desktopWindowFromExpandedViewDragZoneHeight = 0
+    private var splitFromBubbleDragZoneHeight = 0
+    private var splitFromBubbleDragZoneWidth = 0
+    private var hSplitFromExpandedViewDragZoneWidth = 0
+    private var vSplitFromExpandedViewDragZoneWidth = 0
+    private var vSplitFromExpandedViewDragZoneHeightTablet = 0
+    private var vSplitFromExpandedViewDragZoneHeightFoldTall = 0
+    private var vSplitFromExpandedViewDragZoneHeightFoldShort = 0
+
+    init {
+        onConfigurationUpdated()
+    }
+
+    /** Updates all dimensions after a configuration change. */
+    fun onConfigurationUpdated() {
+        dismissDragZoneSize =
+            if (deviceConfig.isSmallTablet) {
+                context.resolveDimension(R.dimen.drag_zone_dismiss_fold)
+            } else {
+                context.resolveDimension(R.dimen.drag_zone_dismiss_tablet)
+            }
+        bubbleDragZoneTabletSize = context.resolveDimension(R.dimen.drag_zone_bubble_tablet)
+        bubbleDragZoneFoldableSize = context.resolveDimension(R.dimen.drag_zone_bubble_fold)
+        fullScreenDragZoneWidth = context.resolveDimension(R.dimen.drag_zone_full_screen_width)
+        fullScreenDragZoneHeight = context.resolveDimension(R.dimen.drag_zone_full_screen_height)
+        desktopWindowDragZoneWidth =
+            context.resolveDimension(R.dimen.drag_zone_desktop_window_width)
+        desktopWindowDragZoneHeight =
+            context.resolveDimension(R.dimen.drag_zone_desktop_window_height)
+        desktopWindowFromExpandedViewDragZoneWidth =
+            context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_width)
+        desktopWindowFromExpandedViewDragZoneHeight =
+            context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_height)
+        splitFromBubbleDragZoneHeight =
+            context.resolveDimension(R.dimen.drag_zone_split_from_bubble_height)
+        splitFromBubbleDragZoneWidth =
+            context.resolveDimension(R.dimen.drag_zone_split_from_bubble_width)
+        hSplitFromExpandedViewDragZoneWidth =
+            context.resolveDimension(R.dimen.drag_zone_h_split_from_expanded_view_width)
+        vSplitFromExpandedViewDragZoneWidth =
+            context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_width)
+        vSplitFromExpandedViewDragZoneHeightTablet =
+            context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_tablet)
+        vSplitFromExpandedViewDragZoneHeightFoldTall =
+            context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall)
+        vSplitFromExpandedViewDragZoneHeightFoldShort =
+            context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short)
+    }
+
+    private fun Context.resolveDimension(@DimenRes dimension: Int) =
+        resources.getDimensionPixelSize(dimension)
 
     /**
      * Creates the list of drag zones for the dragged object.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index f234ff5c2c..126ab3d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ActivityInfo
 import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
 import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
+import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
 import android.window.DesktopModeFlags
 import com.android.internal.R
 import com.android.window.flags.Flags
@@ -49,7 +50,7 @@
         numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
         DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
                 && ((isSystemUiTask(packageName)
-                || isPartOfDefaultHomePackage(packageName)
+                || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName)
                 || isTransparentTask(isActivityStackTransparent, numActivities))
                 && !isTopActivityNoDisplay)
 
@@ -59,13 +60,16 @@
      * The treatment is enabled when all the of the following is true:
      * * Any flags to forcibly consume caption insets are enabled.
      * * Top activity have configuration coupled with insets.
-     * * Task is not resizeable.
+     * * Task is not resizeable or [ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS]
+     * is enabled.
      */
     fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean =
         Flags.excludeCaptionFromAppBounds()
                 && isAnyForceConsumptionFlagsEnabled()
                 && taskInfo.topActivityInfo?.let {
-            isInsetsCoupledWithConfiguration(it) && !taskInfo.isResizeable
+            isInsetsCoupledWithConfiguration(it) && (!taskInfo.isResizeable || it.isChangeEnabled(
+                OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
+            ))
         } ?: false
 
     /**
@@ -81,10 +85,11 @@
     private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
 
     /**
-     * Returns true if the tasks base activity is part of the default home package.
+     * Returns true if the tasks base activity is part of the default home package, or there is
+     * currently no default home package available.
      */
-    private fun isPartOfDefaultHomePackage(packageName: String?) =
-        packageName != null && packageName == defaultHomePackage
+    private fun isPartOfDefaultHomePackageOrNoHomeAvailable(packageName: String?) =
+        defaultHomePackage == null || (packageName != null && packageName == defaultHomePackage)
 
     private fun isAnyForceConsumptionFlagsEnabled(): Boolean =
         DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b2b99d6..5de49b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -914,12 +914,15 @@
             Context context,
             Transitions transitions,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             InteractionJankMonitor interactionJankMonitor) {
         return ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue()
                 ? new SpringDragToDesktopTransitionHandler(
-                        context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+                context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+                interactionJankMonitor)
                 : new DefaultDragToDesktopTransitionHandler(
-                        context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+                        context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+                        interactionJankMonitor);
     }
 
     @WMSingleton
@@ -1303,7 +1306,8 @@
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             DesktopWindowingEducationTooltipController desktopWindowingEducationTooltipController,
             @ShellMainThread CoroutineScope applicationScope,
-            @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher) {
+            @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher,
+            DesktopModeUiEventLogger desktopModeUiEventLogger) {
         return new AppHandleEducationController(
                 context,
                 appHandleEducationFilter,
@@ -1311,7 +1315,8 @@
                 windowDecorCaptionHandleRepository,
                 desktopWindowingEducationTooltipController,
                 applicationScope,
-                backgroundDispatcher);
+                backgroundDispatcher,
+                desktopModeUiEventLogger);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index b96b9d2..b9cb32d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -149,7 +149,25 @@
         @UiEvent(doc = "Enter multi-instance by using the New Window button")
         DESKTOP_WINDOW_MULTI_INSTANCE_NEW_WINDOW_CLICK(2069),
         @UiEvent(doc = "Enter multi-instance by clicking an icon in the Manage Windows menu")
-        DESKTOP_WINDOW_MULTI_INSTANCE_MANAGE_WINDOWS_ICON_CLICK(2070);
+        DESKTOP_WINDOW_MULTI_INSTANCE_MANAGE_WINDOWS_ICON_CLICK(2070),
+        @UiEvent(doc = "Education tooltip on the app handle is shown")
+        APP_HANDLE_EDUCATION_TOOLTIP_SHOWN(2097),
+        @UiEvent(doc = "Education tooltip on the app handle is clicked")
+        APP_HANDLE_EDUCATION_TOOLTIP_CLICKED(2098),
+        @UiEvent(doc = "Education tooltip on the app handle is dismissed by the user")
+        APP_HANDLE_EDUCATION_TOOLTIP_DISMISSED(2099),
+        @UiEvent(doc = "Enter desktop mode education tooltip on the app handle menu is shown")
+        ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN(2100),
+        @UiEvent(doc = "Enter desktop mode education tooltip on the app handle menu is clicked")
+        ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED(2101),
+        @UiEvent(doc = "Enter desktop mode education tooltip is dismissed by the user")
+        ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED(2102),
+        @UiEvent(doc = "Exit desktop mode education tooltip on the app header menu is shown")
+        EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN(2103),
+        @UiEvent(doc = "Exit desktop mode education tooltip on the app header menu is clicked")
+        EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED(2104),
+        @UiEvent(doc = "Exit desktop mode education tooltip is dismissed by the user")
+        EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED(2105);
 
         override fun getId(): Int = mId
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 7491abd..531304d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1659,11 +1659,16 @@
     private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
         logV("addWallpaperActivity")
         if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
+
+            // If the wallpaper activity for this display already exists, let's reorder it to top.
+            val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
+            if (wallpaperActivityToken != null) {
+                wct.reorder(wallpaperActivityToken, /* onTop= */ true)
+                return
+            }
+
             val intent = Intent(context, DesktopWallpaperActivity::class.java)
-            if (
-                desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
-                    Flags.enablePerDisplayDesktopWallpaperActivity()
-            ) {
+            if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
index a5ba661..c10752d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -90,6 +90,11 @@
         return desktopRepoByUserId.getOrCreate(profileId)
     }
 
+    fun getUserIdForProfile(profileId: Int): Int {
+        if (userIdToProfileIdsMap[userId]?.contains(profileId) == true) return userId
+        else return profileId
+    }
+
     /** Dumps [DesktopRepository] for each user. */
     fun dump(pw: PrintWriter, prefix: String) {
         desktopRepoByUserId.forEach { key, value ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2ac76f3..8194d3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -70,6 +70,7 @@
     private val context: Context,
     private val transitions: Transitions,
     private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    private val desktopUserRepositories: DesktopUserRepositories,
     protected val interactionJankMonitor: InteractionJankMonitor,
     protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
 ) : TransitionHandler {
@@ -127,15 +128,18 @@
                 pendingIntentCreatorBackgroundActivityStartMode =
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
             }
-        val taskUser = UserHandle.of(taskInfo.userId)
+        // If we are launching home for a profile of a user, just use the [userId] of that user
+        // instead of the [profileId] to create the context.
+        val userToLaunchWith =
+            UserHandle.of(desktopUserRepositories.getUserIdForProfile(taskInfo.userId))
         val pendingIntent =
             PendingIntent.getActivityAsUser(
-                context.createContextAsUser(taskUser, /* flags= */ 0),
+                context.createContextAsUser(userToLaunchWith, /* flags= */ 0),
                 /* requestCode= */ 0,
                 launchHomeIntent,
                 FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
                 options.toBundle(),
-                taskUser,
+                userToLaunchWith,
             )
         val wct = WindowContainerTransaction()
         // The app that is being dragged into desktop mode might cause new transitions, make this
@@ -881,6 +885,7 @@
     context: Context,
     transitions: Transitions,
     taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    desktopUserRepositories: DesktopUserRepositories,
     interactionJankMonitor: InteractionJankMonitor,
     transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
         SurfaceControl.Transaction()
@@ -890,6 +895,7 @@
         context,
         transitions,
         taskDisplayAreaOrganizer,
+        desktopUserRepositories,
         interactionJankMonitor,
         transactionSupplier,
     ) {
@@ -917,6 +923,7 @@
     context: Context,
     transitions: Transitions,
     taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    desktopUserRepositories: DesktopUserRepositories,
     interactionJankMonitor: InteractionJankMonitor,
     transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
         SurfaceControl.Transaction()
@@ -926,6 +933,7 @@
         context,
         transitions,
         taskDisplayAreaOrganizer,
+        desktopUserRepositories,
         interactionJankMonitor,
         transactionSupplier,
     ) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index 5d83556..f664514 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -26,6 +26,8 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
 import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread
@@ -62,6 +64,7 @@
     private val windowingEducationViewController: DesktopWindowingEducationTooltipController,
     @ShellMainThread private val applicationCoroutineScope: CoroutineScope,
     @ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
+    private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
 ) {
     private lateinit var openHandleMenuCallback: (Int) -> Unit
     private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
@@ -171,6 +174,7 @@
 
     private fun showEducation(captionState: CaptionState) {
         val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
+        val taskInfo = captionState.runningTaskInfo
         val tooltipGlobalCoordinates =
             Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
         // Populate information important to inflate app handle education tooltip.
@@ -188,22 +192,34 @@
                 arrowDirection =
                     DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
                 onEducationClickAction = {
-                    openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+                    openHandleMenuCallback(taskInfo.taskId)
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_CLICKED,
+                    )
                 },
                 onDismissAction = {
-                    // TODO: b/341320146 - Log previous tooltip was dismissed
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_DISMISSED,
+                    )
                 },
             )
 
         windowingEducationViewController.showEducationTooltip(
             tooltipViewConfig = appHandleTooltipConfig,
-            taskId = captionState.runningTaskInfo.taskId,
+            taskId = taskInfo.taskId,
+        )
+        desktopModeUiEventLogger.log(
+            taskInfo,
+            DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_SHOWN,
         )
     }
 
     /** Show tooltip that points to windowing image button in app handle menu */
     private suspend fun showWindowingImageButtonTooltip(captionState: CaptionState.AppHandle) {
         val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
+        val taskInfo = captionState.runningTaskInfo
         val windowingOptionPillHeight =
             getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
         val appHandleMenuWidth =
@@ -245,24 +261,36 @@
                     DesktopWindowingEducationTooltipController.TooltipArrowDirection.HORIZONTAL,
                 onEducationClickAction = {
                     toDesktopModeCallback(
-                        captionState.runningTaskInfo.taskId,
+                        taskInfo.taskId,
                         DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
                     )
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED,
+                    )
                 },
                 onDismissAction = {
-                    // TODO: b/341320146 - Log previous tooltip was dismissed
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED,
+                    )
                 },
             )
 
         windowingEducationViewController.showEducationTooltip(
-            taskId = captionState.runningTaskInfo.taskId,
+            taskId = taskInfo.taskId,
             tooltipViewConfig = windowingImageButtonTooltipConfig,
         )
+        desktopModeUiEventLogger.log(
+            taskInfo,
+            DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN,
+        )
     }
 
     /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
     private suspend fun showExitWindowingTooltip(captionState: CaptionState.AppHeader) {
         val globalAppChipBounds = captionState.globalAppChipBounds
+        val taskInfo = captionState.runningTaskInfo
         val tooltipGlobalCoordinates =
             Point(
                 if (isRtl()) {
@@ -287,16 +315,27 @@
                 arrowDirection =
                     DesktopWindowingEducationTooltipController.TooltipArrowDirection.HORIZONTAL,
                 onDismissAction = {
-                    // TODO: b/341320146 - Log previous tooltip was dismissed
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED,
+                    )
                 },
                 onEducationClickAction = {
-                    openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+                    openHandleMenuCallback(taskInfo.taskId)
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED,
+                    )
                 },
             )
         windowingEducationViewController.showEducationTooltip(
-            taskId = captionState.runningTaskInfo.taskId,
+            taskId = taskInfo.taskId,
             tooltipViewConfig = exitWindowingTooltipConfig,
         )
+        desktopModeUiEventLogger.log(
+            taskInfo,
+            DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN,
+        )
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
index b4cf890..88ac865 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
@@ -26,6 +26,7 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.hardware.HardwareBuffer;
 import android.util.TypedValue;
 import android.view.SurfaceControl;
 
@@ -39,7 +40,6 @@
 
     private final Context mContext;
     private final int mAppIconSizePx;
-    private final Rect mAppBounds;
     private final int mOverlayHalfSize;
     private final Matrix mTmpTransform = new Matrix();
     private final float[] mTmpFloat9 = new float[9];
@@ -56,10 +56,6 @@
         final int overlaySize = getOverlaySize(appBounds, destinationBounds);
         mOverlayHalfSize = overlaySize >> 1;
 
-        // When the activity is in the secondary split, make sure the scaling center is not
-        // offset.
-        mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
-
         mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
         prepareAppIconOverlay(appIcon);
         mLeash = new SurfaceControl.Builder()
@@ -85,12 +81,17 @@
 
     @Override
     public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+        final HardwareBuffer buffer = mBitmap.getHardwareBuffer();
         tx.show(mLeash);
         tx.setLayer(mLeash, Integer.MAX_VALUE);
-        tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+        tx.setBuffer(mLeash, buffer);
         tx.setAlpha(mLeash, 0f);
         tx.reparent(mLeash, parentLeash);
         tx.apply();
+        // Cleanup the bitmap and buffer after setting up the leash
+        mBitmap.recycle();
+        mBitmap = null;
+        buffer.close();
     }
 
     @Override
@@ -108,16 +109,6 @@
                 .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
     }
 
-
-
-    @Override
-    public void detach(SurfaceControl.Transaction tx) {
-        super.detach(tx);
-        if (mBitmap != null && !mBitmap.isRecycled()) {
-            mBitmap.recycle();
-        }
-    }
-
     private void prepareAppIconOverlay(Drawable appIcon) {
         final Canvas canvas = new Canvas();
         canvas.setBitmap(mBitmap);
@@ -139,6 +130,8 @@
                 mOverlayHalfSize + mAppIconSizePx / 2);
         appIcon.setBounds(appIconBounds);
         appIcon.draw(canvas);
+        Bitmap oldBitmap = mBitmap;
         mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+        oldBitmap.recycle();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index bb9b479..a57b4b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -72,7 +72,6 @@
 import com.android.wm.shell.pip2.animation.PipEnterAnimator;
 import com.android.wm.shell.pip2.animation.PipExpandAnimator;
 import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -422,7 +421,7 @@
         final Rect destinationBounds = pipChange.getEndAbsBounds();
         final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
         if (swipePipToHomeOverlay != null) {
-            final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
+            final int overlaySize = PipAppIconOverlay.getOverlaySize(
                     mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
             // It is possible we reparent the PIP activity to a new PIP task (in multi-activity
             // apps), so we should also reparent the overlay to the final PIP task.
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 15ac03c..a799b7f 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
@@ -2859,14 +2859,6 @@
                     prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED);
                     mSplitTransitions.setDismissTransition(transition, dismissTop,
                             EXIT_REASON_APP_FINISHED);
-                } else if (isOpening && !mPausingTasks.isEmpty()) {
-                    // One of the splitting task is opening while animating the split pair in
-                    // recents, which means to dismiss the split pair to this task.
-                    int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
-                            ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
-                    prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED);
-                    mSplitTransitions.setDismissTransition(transition, dismissTop,
-                            EXIT_REASON_APP_FINISHED);
                 } else if (!isSplitScreenVisible() && isOpening) {
                     // If split is running in the background and the trigger task is appearing into
                     // split, prepare to enter split screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index a5a5fd7..01428e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -149,8 +149,7 @@
 
     private Drawable mEnterpriseThumbnailDrawable;
 
-    static final InteractionJankMonitor sInteractionJankMonitor =
-            InteractionJankMonitor.getInstance();
+    final InteractionJankMonitor mInteractionJankMonitor;
 
     private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
         @Override
@@ -169,7 +168,8 @@
             @NonNull TransactionPool transactionPool,
             @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor,
-            @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
+            @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+            @NonNull InteractionJankMonitor interactionJankMonitor) {
         mDisplayController = displayController;
         mTransactionPool = transactionPool;
         mContext = context;
@@ -181,6 +181,7 @@
         mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
         shellInit.addInitCallback(this::onInit, this);
         mRootTDAOrganizer = rootTDAOrganizer;
+        mInteractionJankMonitor = interactionJankMonitor;
     }
 
     private void onInit() {
@@ -331,14 +332,14 @@
 
         final boolean isTaskTransition = isTaskTransition(info);
         if (isTaskTransition) {
-            sInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext,
+            mInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext,
                     mMainHandler, CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
         }
 
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
             if (isTaskTransition) {
-                sInteractionJankMonitor.end(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
+                mInteractionJankMonitor.end(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
             }
             mAnimations.remove(transition);
             finishCallback.onTransitionFinished(null /* wct */);
@@ -1031,6 +1032,6 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
                               @Nullable SurfaceControl.Transaction finishTransaction) {
-        sInteractionJankMonitor.cancel(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
+        mInteractionJankMonitor.cancel(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
     }
 }
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 c90f6cf..deb8839 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
@@ -77,6 +77,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -335,7 +336,8 @@
         mDisplayController = displayController;
         mPlayerImpl = new TransitionPlayerImpl();
         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
-                displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
+                displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer,
+                InteractionJankMonitor.getInstance());
         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index c92e67f..ff50672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -35,9 +35,10 @@
 import android.view.View
 import android.view.WindowInsets.Type.systemBars
 import android.view.WindowManager
-import android.widget.Button
 import android.widget.ImageButton
 import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.Space
 import android.widget.TextView
 import android.window.DesktopModeFlags
 import android.window.SurfaceSyncGroup
@@ -482,16 +483,23 @@
         private val splitscreenBtn = windowingPill.requireViewById<ImageButton>(
             R.id.split_screen_button)
         private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button)
+        private val floatingBtnSpace = windowingPill.requireViewById<Space>(
+            R.id.floating_button_space)
+
         private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button)
+        private val desktopBtnSpace = windowingPill.requireViewById<Space>(
+            R.id.desktop_button_space)
 
         // More Actions Pill.
         private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
-        private val screenshotBtn = moreActionsPill.requireViewById<View>(R.id.screenshot_button)
-        private val newWindowBtn = moreActionsPill.requireViewById<View>(R.id.new_window_button)
+        private val screenshotBtn = moreActionsPill.requireViewById<HandleMenuActionButton>(
+            R.id.screenshot_button)
+        private val newWindowBtn = moreActionsPill.requireViewById<HandleMenuActionButton>(
+            R.id.new_window_button)
         private val manageWindowBtn = moreActionsPill
-            .requireViewById<View>(R.id.manage_windows_button)
+            .requireViewById<HandleMenuActionButton>(R.id.manage_windows_button)
         private val changeAspectRatioBtn = moreActionsPill
-            .requireViewById<View>(R.id.change_aspect_ratio_button)
+            .requireViewById<HandleMenuActionButton>(R.id.change_aspect_ratio_button)
 
         // Open in Browser/App Pill.
         private val openInAppOrBrowserPill = rootView.requireViewById<View>(
@@ -540,17 +548,35 @@
                 return@setOnTouchListener true
             }
 
-            with(context.resources) {
-                // Update a11y read out to say "double tap to enter desktop windowing mode"
+            with(context) {
+                // Update a11y announcement out to say "double tap to enter Fullscreen"
                 ViewCompat.replaceAccessibilityAction(
-                    desktopBtn, ACTION_CLICK,
-                    getString(R.string.app_handle_menu_talkback_desktop_mode_button_text), null
+                    fullscreenBtn, ACTION_CLICK,
+                    getString(
+                        R.string.app_handle_menu_accessibility_announce,
+                        getString(R.string.fullscreen_text)
+                    ),
+                    null,
                 )
 
-                // Update a11y read out to say "double tap to enter split screen mode"
+                // Update a11y announcement out to say "double tap to enter Desktop View"
+                ViewCompat.replaceAccessibilityAction(
+                    desktopBtn, ACTION_CLICK,
+                    getString(
+                        R.string.app_handle_menu_accessibility_announce,
+                        getString(R.string.desktop_text)
+                    ),
+                    null,
+                )
+
+                // Update a11y announcement to say "double tap to enter Split Screen"
                 ViewCompat.replaceAccessibilityAction(
                     splitscreenBtn, ACTION_CLICK,
-                    getString(R.string.app_handle_menu_talkback_split_screen_mode_button_text), null
+                    getString(
+                        R.string.app_handle_menu_accessibility_announce,
+                        getString(R.string.split_screen_text)
+                    ),
+                    null,
                 )
             }
         }
@@ -666,6 +692,7 @@
 
             if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
                 floatingBtn.visibility = View.GONE
+                floatingBtnSpace.visibility = View.GONE
             }
 
             fullscreenBtn.isSelected = taskInfo.isFullscreen
@@ -694,8 +721,10 @@
             ).forEach {
                 val button = it.first
                 val shouldShow = it.second
-                val label = button.requireViewById<MarqueedTextView>(R.id.label)
-                val image = button.requireViewById<ImageView>(R.id.image)
+
+                val buttonRoot = button.requireViewById<LinearLayout>(R.id.action_button)
+                val label = buttonRoot.requireViewById<MarqueedTextView>(R.id.label)
+                val image = buttonRoot.requireViewById<ImageView>(R.id.image)
 
                 button.isGone = !shouldShow
                 label.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
new file mode 100644
index 0000000..4b2e473
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.annotation.ColorInt
+import android.annotation.IdRes
+import android.content.Context
+import android.content.res.ColorStateList
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.content.withStyledAttributes
+import androidx.core.view.isGone
+import com.android.wm.shell.R
+
+/**
+ * Button-like component used to display the "Additional options" elements of the Handle menu window
+ *  decoration.
+ *
+ * The possible options for which this button is used for are "Screenshot", "New Window", "Manage
+ * Windows" and "Change Aspect Ratio".
+ */
+class HandleMenuActionButton @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr) {
+
+    private val rootElement: LinearLayout
+    private val iconView: ImageView
+    private val textView: MarqueedTextView
+
+    init {
+        val view = LayoutInflater.from(context).inflate(
+            R.layout.desktop_mode_window_decor_handle_menu_action_button, this, true)
+        rootElement = findViewById(R.id.action_button)
+        iconView = findViewById(R.id.image)
+        textView = findViewById(R.id.label)
+
+        context.withStyledAttributes(attrs, R.styleable.HandleMenuActionButton) {
+            textView.text = getString(R.styleable.HandleMenuActionButton_android_text)
+            textView.setTextColor(getColor(R.styleable.HandleMenuActionButton_android_textColor, 0))
+            iconView.setImageResource(getResourceId(
+                R.styleable.HandleMenuActionButton_android_src, 0))
+            iconView.imageTintList = getColorStateList(
+                R.styleable.HandleMenuActionButton_android_drawableTint)
+        }
+    }
+
+    /**
+     * Sets a listener to be invoked when this view is clicked.
+     *
+     * @param l the [OnClickListener] that receives click events.
+     */
+    override fun setOnClickListener(l: OnClickListener?) {
+        rootElement.setOnClickListener(l)
+    }
+
+    /**
+     * Sets the text color for the text inside the button.
+     *
+     * @param color the color to set for the text, as a color integer.
+     */
+    fun setTextColor(@ColorInt color: Int) {
+        textView.setTextColor(color)
+    }
+
+    /**
+     * Sets the icon for the button using a resource ID.
+     *
+     * @param resourceId the resource ID of the drawable to set as the icon.
+     */
+    fun setIconResource(@IdRes resourceId: Int) {
+        iconView.setImageResource(resourceId)
+    }
+
+    /**
+     * Sets the text to display inside the button.
+     *
+     * @param text the text to display.
+     */
+    fun setText(text: CharSequence?) {
+        textView.text = text
+    }
+
+    /**
+     * Sets the tint color for the icon.
+     *
+     * @param color the color to use for the tint, as a color integer.
+     */
+    fun setDrawableTint(@ColorInt color: Int) {
+        iconView.imageTintList = ColorStateList.valueOf(color)
+    }
+
+    /**
+     * Gets or sets the tint applied to the icon.
+     *
+     * @return The [ColorStateList] representing the tint, or null if no tint is applied.
+     */
+    var compoundDrawableTintList: ColorStateList?
+        get() = iconView.imageTintList
+        set(value) {
+            iconView.imageTintList = value
+        }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 4762bc2..90c865e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -93,9 +93,6 @@
     private val lightColors = dynamicLightColorScheme(context)
     private val darkColors = dynamicDarkColorScheme(context)
 
-    private val headerButtonOpenMenuA11yText = context.resources
-        .getString(R.string.desktop_mode_app_header_chip_text)
-
     /**
      * The corner radius to apply to the app chip, maximize and close button's background drawable.
      **/
@@ -231,35 +228,29 @@
             }
         }
 
-        val a11yActionOpenHeaderMenu = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK,
-            headerButtonOpenMenuA11yText)
-        openMenuButton.accessibilityDelegate = object : View.AccessibilityDelegate() {
-            override fun onInitializeAccessibilityNodeInfo(
-                host: View,
-                info: AccessibilityNodeInfo
-            ) {
-                super.onInitializeAccessibilityNodeInfo(host, info)
-                info.addAction(a11yActionOpenHeaderMenu)
-            }
-        }
+        // Update a11y announcement to say "double tap to open menu"
+        ViewCompat.replaceAccessibilityAction(
+            openMenuButton,
+            AccessibilityActionCompat.ACTION_CLICK,
+            context.getString(R.string.app_handle_chip_accessibility_announce),
+            null
+        )
 
-        with(context.resources) {
-            // Update a11y read out to say "double tap to maximize or restore window size"
-            ViewCompat.replaceAccessibilityAction(
-                maximizeWindowButton,
-                AccessibilityActionCompat.ACTION_CLICK,
-                getString(R.string.maximize_button_talkback_action_maximize_restore_text),
-                null
-            )
+        // Update a11y announcement to say "double tap to maximize or restore window size"
+        ViewCompat.replaceAccessibilityAction(
+            maximizeWindowButton,
+            AccessibilityActionCompat.ACTION_CLICK,
+            context.getString(R.string.maximize_button_talkback_action_maximize_restore_text),
+            null
+        )
 
-            // Update a11y read out to say "double tap to minimize app window"
-            ViewCompat.replaceAccessibilityAction(
-                minimizeWindowButton,
-                AccessibilityActionCompat.ACTION_CLICK,
-                getString(R.string.minimize_button_talkback_action_maximize_restore_text),
-                null
-            )
-        }
+        // Update a11y announcement out to say "double tap to minimize app window"
+        ViewCompat.replaceAccessibilityAction(
+            minimizeWindowButton,
+            AccessibilityActionCompat.ACTION_CLICK,
+            context.getString(R.string.minimize_button_talkback_action_maximize_restore_text),
+            null
+        )
     }
 
     override fun bindData(data: HeaderData) {
@@ -275,7 +266,8 @@
     /** Sets the app's name in the header. */
     fun setAppName(name: CharSequence) {
         appNameTextView.text = name
-        openMenuButton.contentDescription = name
+        openMenuButton.contentDescription =
+            context.getString(R.string.desktop_mode_app_header_chip_text, name)
     }
 
     /** Sets the app's icon in the header. */
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 02b2cec..ae73dae 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -53,10 +53,12 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
         <option name="teardown-command"
                 value="settings delete secure show_ime_with_hard_keyboard"/>
         <option name="teardown-command" value="settings delete system show_touches"/>
         <option name="teardown-command" value="settings delete system pointer_location"/>
+        <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
         <option name="teardown-command"
                 value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
     </target_preparer>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 98c5bc96..be53272 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -51,6 +51,7 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableContext
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.DragEvent
 import android.view.Gravity
@@ -147,6 +148,7 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
 import com.android.wm.shell.transition.Transitions.TransitionHandler
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
 import com.google.common.truth.Truth.assertThat
@@ -171,7 +173,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.isA
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
@@ -187,7 +188,6 @@
 import org.mockito.kotlin.argThat
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.atLeastOnce
-import org.mockito.kotlin.capture
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
@@ -257,6 +257,7 @@
     @Mock private lateinit var desksOrganizer: DesksOrganizer
     @Mock private lateinit var userProfileContexts: UserProfileContexts
     @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
+    @Mock private lateinit var packageManager: PackageManager
 
     private lateinit var controller: DesktopTasksController
     private lateinit var shellInit: ShellInit
@@ -266,6 +267,7 @@
     private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
     private lateinit var testScope: CoroutineScope
     private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+    private lateinit var spyContext: TestableContext
 
     private val shellExecutor = TestShellExecutor()
 
@@ -282,6 +284,7 @@
     private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611)
     private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
     private val wallpaperToken = MockToken().token()
+    private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
 
     @Before
     fun setUp() {
@@ -295,6 +298,7 @@
         doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
 
         testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+        spyContext = spy(mContext)
         shellInit = spy(ShellInit(testExecutor))
         userRepositories =
             DesktopUserRepositories(
@@ -316,7 +320,7 @@
                 mContext,
                 mockHandler,
             )
-        desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(context))
+        desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext))
 
         whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
         whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -363,9 +367,9 @@
 
         shellInit.init()
 
-        val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+        val captor = argumentCaptor<RecentsTransitionStateListener>()
         verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
-        recentsTransitionStateListener = captor.value
+        recentsTransitionStateListener = captor.firstValue
 
         controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
 
@@ -374,6 +378,9 @@
         taskRepository = userRepositories.current
         taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
         taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+
+        spyContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(homeComponentName)
     }
 
     private fun createController() =
@@ -441,7 +448,7 @@
     fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
         val task1 = setUpFreeformTask()
 
-        val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+        val argumentCaptor = argumentCaptor<Boolean>()
         controller.toggleDesktopTaskSize(
             task1,
             ToggleTaskSizeInteraction(
@@ -461,7 +468,7 @@
                 STABLE_BOUNDS.height(),
                 displayController,
             )
-        assertThat(argumentCaptor.value).isTrue()
+        assertThat(argumentCaptor.firstValue).isTrue()
     }
 
     @Test
@@ -476,7 +483,7 @@
         val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
         val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
 
-        val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+        val argumentCaptor = argumentCaptor<Boolean>()
         controller.toggleDesktopTaskSize(
             task1,
             ToggleTaskSizeInteraction(
@@ -497,7 +504,7 @@
                 eq(displayController),
                 anyOrNull(),
             )
-        assertThat(argumentCaptor.value).isFalse()
+        assertThat(argumentCaptor.firstValue).isFalse()
     }
 
     @Test
@@ -547,6 +554,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         markTaskHidden(task1)
@@ -581,7 +589,7 @@
         val wct =
             getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
         // Wallpaper is moved to front.
-        wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+        wct.assertReorderAt(index = 0, wallpaperToken)
         // Desk is activated.
         verify(desksOrganizer).activateDesk(wct, deskId)
     }
@@ -783,6 +791,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         markTaskVisible(task1)
@@ -825,7 +834,8 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
-    fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+    fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         markTaskHidden(task1)
@@ -842,6 +852,24 @@
         wct.assertReorderAt(index = 2, task2)
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+        val task1 = setUpFreeformTask()
+        val task2 = setUpFreeformTask()
+        markTaskHidden(task1)
+        markTaskVisible(task2)
+
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+        assertThat(wct.hierarchyOps).hasSize(3)
+        // Expect order to be from bottom: wallpaper intent, task1, task2
+        wct.assertReorderAt(index = 0, wallpaperToken)
+        wct.assertReorderAt(index = 1, task1)
+        wct.assertReorderAt(index = 2, task2)
+    }
+
     @Test
     @DisableFlags(
         Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
@@ -860,9 +888,9 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
-    fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
-        whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
-            .thenReturn(Binder())
+    fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
         val wct =
@@ -871,10 +899,18 @@
     }
 
     @Test
-    @DisableFlags(
-        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
-        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
-    )
+    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+        wct.assertReorderAt(index = 0, wallpaperToken)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
         taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
         val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
@@ -899,6 +935,7 @@
     fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
         whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
             .thenReturn(Binder())
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
         val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
         val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -991,6 +1028,7 @@
     /** TODO: b/362720497 - add multi-desk version when minimization is implemented. */
     @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val homeTask = setUpHomeTask()
         val freeformTask = setUpFreeformTask()
         val minimizedTask = setUpFreeformTask()
@@ -1569,6 +1607,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task = createTaskInfo(1)
         whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
         whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1736,7 +1775,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
-        val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+        val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
         whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
             .thenReturn(Binder())
 
@@ -1751,12 +1790,12 @@
 
         verify(desktopModeEnterExitTransitionListener)
             .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
-        assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+        assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
     }
 
     @Test
     fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
-        val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+        val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
         whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
             .thenReturn(Binder())
 
@@ -1768,7 +1807,7 @@
 
         verify(desktopModeEnterExitTransitionListener)
             .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
-        assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+        assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
     }
 
     @Test
@@ -1802,6 +1841,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val freeformTask = setUpFreeformTask()
         val fullscreenTask = setUpFullscreenTask()
         markTaskHidden(freeformTask)
@@ -1828,6 +1868,7 @@
     @EnableFlags(
         Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
         Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+        Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
     )
     fun moveRunningTaskToDesktop_desktopWallpaperEnabled_multiDesksEnabled() {
         val freeformTask = setUpFreeformTask()
@@ -1840,7 +1881,7 @@
         )
 
         val wct = getLatestEnterDesktopWct()
-        wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+        wct.assertReorderAt(index = 0, wallpaperToken)
         verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, fullscreenTask)
         verify(desksOrganizer).activateDesk(wct, deskId = 0)
         verify(desktopModeEnterExitTransitionListener)
@@ -1967,6 +2008,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
         val newTask = setUpFullscreenTask()
         val homeTask = setUpHomeTask()
@@ -2224,26 +2266,26 @@
     fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
         setUpHomeTask()
         val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
-        val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+        val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
         whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
             .thenReturn(Binder())
 
         controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
 
-        assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+        assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
     }
 
     @Test
     fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
         setUpHomeTask()
         val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
-        val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+        val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
         whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
             .thenReturn(Binder())
 
         controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
 
-        assertThat(transitionHandlerArgCaptor.value)
+        assertThat(transitionHandlerArgCaptor.firstValue)
             .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
     }
 
@@ -2718,9 +2760,9 @@
 
         controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
-        captor.value.hierarchyOps.none { hop ->
+        captor.firstValue.hierarchyOps.none { hop ->
             hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
         }
     }
@@ -2759,9 +2801,9 @@
 
         controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
-        captor.value.hierarchyOps.none { hop ->
+        captor.firstValue.hierarchyOps.none { hop ->
             hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
         }
     }
@@ -2775,9 +2817,9 @@
 
         controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
-        captor.value.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
+        captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
     }
 
     @Test
@@ -2791,10 +2833,10 @@
         // The only active task is being minimized.
         controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
         // Adds remove wallpaper operation
-        captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+        captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
     }
 
     @Test
@@ -2808,9 +2850,9 @@
         // The only active task is already minimized.
         controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
-        captor.value.hierarchyOps.none { hop ->
+        captor.firstValue.hierarchyOps.none { hop ->
             hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
         }
     }
@@ -2825,9 +2867,9 @@
 
         controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
 
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
-        captor.value.hierarchyOps.none { hop ->
+        captor.firstValue.hierarchyOps.none { hop ->
             hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
         }
     }
@@ -2845,10 +2887,10 @@
         // task1 is the only visible task as task2 is minimized.
         controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
         // Adds remove wallpaper operation
-        val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
         // Adds remove wallpaper operation
-        captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+        captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
     }
 
     @Test
@@ -2987,6 +3029,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
         val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
         tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -3118,6 +3161,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val freeformTask1 = setUpFreeformTask()
         val freeformTask2 = createFreeformTask()
 
@@ -3152,7 +3196,9 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task = createFreeformTask()
+
         val result = controller.handleRequest(Binder(), createTransition(task))
 
         assertNotNull(result, "Should handle request")
@@ -3180,6 +3226,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
         // Second display task
         createFreeformTask(displayId = SECOND_DISPLAY)
@@ -4635,7 +4682,7 @@
 
         controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
 
-        val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val wctArgument = argumentCaptor<WindowContainerTransaction>()
         verify(splitScreenController)
             .requestEnterSplitSelect(
                 eq(task2),
@@ -4643,9 +4690,9 @@
                 eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
                 eq(task2.configuration.windowConfiguration.bounds),
             )
-        assertThat(wctArgument.value.hierarchyOps).hasSize(1)
+        assertThat(wctArgument.firstValue.hierarchyOps).hasSize(1)
         // Removes wallpaper activity when leaving desktop
-        wctArgument.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+        wctArgument.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
     }
 
     @Test
@@ -4660,7 +4707,7 @@
 
         controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
 
-        val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val wctArgument = argumentCaptor<WindowContainerTransaction>()
         verify(splitScreenController)
             .requestEnterSplitSelect(
                 eq(task2),
@@ -4669,7 +4716,7 @@
                 eq(task2.configuration.windowConfiguration.bounds),
             )
         // Does not remove wallpaper activity, as desktop still has visible desktop tasks
-        assertThat(wctArgument.value.hierarchyOps).isEmpty()
+        assertThat(wctArgument.firstValue.hierarchyOps).isEmpty()
     }
 
     @Test
@@ -4677,7 +4724,7 @@
     fun newWindow_fromFullscreenOpensInSplit() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask()
-        val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+        val optionsCaptor = argumentCaptor<Bundle>()
         runOpenNewWindow(task)
         verify(splitScreenController)
             .startIntent(
@@ -4690,7 +4737,7 @@
                 eq(true),
                 eq(SPLIT_INDEX_UNDEFINED),
             )
-        assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+        assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
             .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
     }
 
@@ -4699,7 +4746,7 @@
     fun newWindow_fromSplitOpensInSplit() {
         setUpLandscapeDisplay()
         val task = setUpSplitScreenTask()
-        val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+        val optionsCaptor = argumentCaptor<Bundle>()
         runOpenNewWindow(task)
         verify(splitScreenController)
             .startIntent(
@@ -4712,7 +4759,7 @@
                 eq(true),
                 eq(SPLIT_INDEX_UNDEFINED),
             )
-        assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+        assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
             .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
     }
 
@@ -4807,11 +4854,11 @@
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask()
         val taskToRequest = setUpFreeformTask()
-        val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+        val optionsCaptor = argumentCaptor<Bundle>()
         runOpenInstance(task, taskToRequest.taskId)
         verify(splitScreenController)
             .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
-        assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+        assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
             .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
     }
 
@@ -4821,11 +4868,11 @@
         setUpLandscapeDisplay()
         val task = setUpSplitScreenTask()
         val taskToRequest = setUpFreeformTask()
-        val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+        val optionsCaptor = argumentCaptor<Bundle>()
         runOpenInstance(task, taskToRequest.taskId)
         verify(splitScreenController)
             .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
-        assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+        assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
             .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
     }
 
@@ -5912,35 +5959,37 @@
             mockDragEvent,
             mockCallback as Consumer<Boolean>,
         )
-        val arg: ArgumentCaptor<WindowContainerTransaction> =
-            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val arg = argumentCaptor<WindowContainerTransaction>()
         var expectedWindowingMode: Int
         if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
             expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
             // Fullscreen launches currently use default transitions
-            verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+            verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
         } else {
             expectedWindowingMode = WINDOWING_MODE_FREEFORM
             if (tabTearingAnimationFlagEnabled) {
                 verify(desktopMixedTransitionHandler)
                     .startLaunchTransition(
                         eq(TRANSIT_OPEN),
-                        capture(arg),
+                        arg.capture(),
                         anyOrNull(),
                         anyOrNull(),
                         anyOrNull(),
                     )
             } else {
                 // All other launches use a special handler.
-                verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+                verify(dragAndDropTransitionHandler).handleDropEvent(arg.capture())
             }
         }
         assertThat(
-                ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+                ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
                     .launchWindowingMode
             )
             .isEqualTo(expectedWindowingMode)
-        assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions).launchBounds)
+        assertThat(
+                ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
+                    .launchBounds
+            )
             .isEqualTo(expectedBounds)
     }
 
@@ -6122,52 +6171,49 @@
         @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
         handlerClass: Class<out TransitionHandler>? = null,
     ): WindowContainerTransaction {
-        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val arg = argumentCaptor<WindowContainerTransaction>()
         if (handlerClass == null) {
             verify(transitions).startTransition(eq(type), arg.capture(), isNull())
         } else {
             verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
         }
-        return arg.value
+        return arg.lastValue
     }
 
     private fun getLatestToggleResizeDesktopTaskWct(
         currentBounds: Rect? = null
     ): WindowContainerTransaction {
-        val arg: ArgumentCaptor<WindowContainerTransaction> =
-            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val arg = argumentCaptor<WindowContainerTransaction>()
         verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
-            .startTransition(capture(arg), eq(currentBounds))
-        return arg.value
+            .startTransition(arg.capture(), eq(currentBounds))
+        return arg.lastValue
     }
 
     private fun getLatestDesktopMixedTaskWct(
         @WindowManager.TransitionType type: Int = TRANSIT_OPEN
     ): WindowContainerTransaction {
-        val arg: ArgumentCaptor<WindowContainerTransaction> =
-            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val arg = argumentCaptor<WindowContainerTransaction>()
         verify(desktopMixedTransitionHandler)
-            .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull())
-        return arg.value
+            .startLaunchTransition(eq(type), arg.capture(), anyOrNull(), anyOrNull(), anyOrNull())
+        return arg.lastValue
     }
 
     private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
-        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val arg = argumentCaptor<WindowContainerTransaction>()
         verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
-        return arg.value
+        return arg.lastValue
     }
 
     private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
-        val arg: ArgumentCaptor<WindowContainerTransaction> =
-            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-        verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
-        return arg.value
+        val arg = argumentCaptor<WindowContainerTransaction>()
+        verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(arg.capture())
+        return arg.lastValue
     }
 
     private fun getLatestExitDesktopWct(): WindowContainerTransaction {
-        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        val arg = argumentCaptor<WindowContainerTransaction>()
         verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
-        return arg.value
+        return arg.lastValue
     }
 
     private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index 83e4872..030bb1ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -123,8 +123,26 @@
         assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2)
     }
 
+    @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+    fun getUserForProfile_flagEnabled_returnsUserIdForProfile() {
+        userRepositories.onUserChanged(USER_ID_2, mock())
+        val profiles: MutableList<UserInfo> =
+            mutableListOf(
+                UserInfo(USER_ID_2, "User profile", 0),
+                UserInfo(PROFILE_ID_1, "Work profile", 0),
+            )
+        userRepositories.onUserProfilesChanged(profiles)
+
+        val userIdForProfile = userRepositories.getUserIdForProfile(PROFILE_ID_1)
+
+        assertThat(userIdForProfile).isEqualTo(USER_ID_2)
+    }
+
     private companion object {
         const val USER_ID_1 = 7
+        const val USER_ID_2 = 8
+        const val PROFILE_ID_1 = 4
         const val PROFILE_ID_2 = 5
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 25246d9..1732875 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -70,6 +70,7 @@
     @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var draggedTaskLeash: SurfaceControl
     @Mock private lateinit var homeTaskLeash: SurfaceControl
+    @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
 
     private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
 
@@ -84,6 +85,7 @@
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
+                    desktopUserRepositories,
                     mockInteractionJankMonitor,
                     transactionSupplier,
                 )
@@ -93,6 +95,7 @@
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
+                    desktopUserRepositories,
                     mockInteractionJankMonitor,
                     transactionSupplier,
                 )
@@ -484,17 +487,22 @@
         val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
         val finishCallback = mock<Transitions.TransitionFinishCallback>()
         val task = createTask()
-        val startTransition = startDrag(
-            springHandler, task, finishTransaction = playingFinishTransaction, homeChange = null)
+        val startTransition =
+            startDrag(
+                springHandler,
+                task,
+                finishTransaction = playingFinishTransaction,
+                homeChange = null,
+            )
         springHandler.onTaskResizeAnimationListener = mock()
 
         springHandler.mergeAnimation(
             transition = mock<IBinder>(),
             info =
-            createTransitionInfo(
-                type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
-                draggedTask = task,
-            ),
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                    draggedTask = task,
+                ),
             startT = mergedStartTransaction,
             finishT = mergedFinishTransaction,
             mergeTarget = startTransition,
@@ -723,7 +731,8 @@
     private fun createTransitionInfo(
         type: Int,
         draggedTask: RunningTaskInfo,
-        homeChange: TransitionInfo.Change? = createHomeChange()) =
+        homeChange: TransitionInfo.Change? = createHomeChange(),
+    ) =
         TransitionInfo(type, /* flags= */ 0).apply {
             homeChange?.let { addChange(it) }
             addChange( // Dragged Task.
@@ -741,11 +750,12 @@
             )
         }
 
-    private fun createHomeChange() = TransitionInfo.Change(mock(), homeTaskLeash).apply {
-        parent = null
-        taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
-        flags = flags or FLAG_IS_WALLPAPER
-    }
+    private fun createHomeChange() =
+        TransitionInfo.Change(mock(), homeTaskLeash).apply {
+            parent = null
+            taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+            flags = flags or FLAG_IS_WALLPAPER
+        }
 
     private fun systemPropertiesKey(name: String) =
         "${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index dfb1b0c..9cb2a05 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.wm.shell.desktopmode.compatui
 
+import android.content.ComponentName
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.Binder
 import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_CLOSE
@@ -37,6 +40,7 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -44,6 +48,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
@@ -62,16 +67,23 @@
     private val desktopRepository = mock<DesktopRepository>()
     private val startT = mock<SurfaceControl.Transaction>()
     private val finishT = mock<SurfaceControl.Transaction>()
+    private val packageManager = mock<PackageManager>()
+    private val componentName = mock<ComponentName>()
 
+    private lateinit var spyContext: TestableContext
     private lateinit var transitionHandler: SystemModalsTransitionHandler
     private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
 
     @Before
     fun setUp() {
+        spyContext = spy(mContext)
         // Simulate having one Desktop task so that we see Desktop Mode as active
         whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
         whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
-        desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+        whenever(spyContext.packageManager).thenReturn(packageManager)
+        whenever(componentName.packageName).thenReturn(HOME_LAUNCHER_PACKAGE_NAME)
+        whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(componentName)
+        desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
         transitionHandler = createTransitionHandler()
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 86e8142..08b9e04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -26,6 +26,8 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_DELAY_MILLIS
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.TOOLTIP_VISIBLE_DURATION_MILLIS
@@ -86,6 +88,7 @@
     @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository
     @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
     @Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController
+    @Mock private lateinit var mockDesktopModeUiEventLogger: DesktopModeUiEventLogger
 
     @Before
     fun setUp() {
@@ -105,6 +108,7 @@
                 mockTooltipController,
                 testScope.backgroundScope,
                 Dispatchers.Main,
+                mockDesktopModeUiEventLogger,
             )
     }
 
@@ -123,6 +127,8 @@
             verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
             verify(mockDataStoreRepository, times(1))
                 .updateAppHandleHintViewedTimestampMillis(eq(true))
+            verify(mockDesktopModeUiEventLogger, times(1))
+                .log(any(), eq(DesktopUiEventEnum.APP_HANDLE_EDUCATION_TOOLTIP_SHOWN))
         }
 
     @Test
@@ -155,6 +161,8 @@
             verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
             verify(mockDataStoreRepository, times(1))
                 .updateEnterDesktopModeHintViewedTimestampMillis(eq(true))
+            verify(mockDesktopModeUiEventLogger, times(1))
+                .log(any(), eq(DesktopUiEventEnum.ENTER_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN))
         }
 
     @Test
@@ -170,6 +178,8 @@
             verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
             verify(mockDataStoreRepository, times(1))
                 .updateExitDesktopModeHintViewedTimestampMillis(eq(true))
+            verify(mockDesktopModeUiEventLogger, times(1))
+                .log(any(), eq(DesktopUiEventEnum.EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_SHOWN))
         }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
index 7cd46af..fd22a84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
@@ -16,8 +16,10 @@
 
 package com.android.wm.shell.shared.bubbles
 
+import android.content.Context
 import android.graphics.Insets
 import android.graphics.Rect
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.shared.bubbles.DragZoneFactory.DesktopWindowModeChecker
@@ -34,6 +36,7 @@
 /** Unit tests for [DragZoneFactory]. */
 class DragZoneFactoryTest {
 
+    private val context = getApplicationContext<Context>()
     private lateinit var dragZoneFactory: DragZoneFactory
     private val tabletPortrait =
         DeviceConfig(
@@ -57,7 +60,12 @@
     @Test
     fun dragZonesForBubbleBar_tablet() {
         dragZoneFactory =
-            DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+            DragZoneFactory(
+                context,
+                tabletPortrait,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(DraggedObject.BubbleBar(BubbleBarLocation.LEFT))
         val expectedZones: List<DragZoneVerifier> =
@@ -73,7 +81,12 @@
     @Test
     fun dragZonesForBubble_tablet_portrait() {
         dragZoneFactory =
-            DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+            DragZoneFactory(
+                context,
+                tabletPortrait,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
         val expectedZones: List<DragZoneVerifier> =
@@ -92,7 +105,13 @@
 
     @Test
     fun dragZonesForBubble_tablet_landscape() {
-        dragZoneFactory = DragZoneFactory(tabletLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                tabletLandscape,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
         val expectedZones: List<DragZoneVerifier> =
@@ -111,7 +130,13 @@
 
     @Test
     fun dragZonesForBubble_foldable_portrait() {
-        dragZoneFactory = DragZoneFactory(foldablePortrait, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                foldablePortrait,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
         val expectedZones: List<DragZoneVerifier> =
@@ -129,7 +154,13 @@
 
     @Test
     fun dragZonesForBubble_foldable_landscape() {
-        dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                foldableLandscape,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
         val expectedZones: List<DragZoneVerifier> =
@@ -148,7 +179,12 @@
     @Test
     fun dragZonesForExpandedView_tablet_portrait() {
         dragZoneFactory =
-            DragZoneFactory(tabletPortrait, splitScreenModeChecker, desktopWindowModeChecker)
+            DragZoneFactory(
+                context,
+                tabletPortrait,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(
                 DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
@@ -169,9 +205,17 @@
 
     @Test
     fun dragZonesForExpandedView_tablet_landscape() {
-        dragZoneFactory = DragZoneFactory(tabletLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                tabletLandscape,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
-            dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+            dragZoneFactory.createSortedDragZones(
+                DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+            )
         val expectedZones: List<DragZoneVerifier> =
             listOf(
                 verifyInstance<DragZone.Dismiss>(),
@@ -188,9 +232,17 @@
 
     @Test
     fun dragZonesForExpandedView_foldable_portrait() {
-        dragZoneFactory = DragZoneFactory(foldablePortrait, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                foldablePortrait,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
-            dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+            dragZoneFactory.createSortedDragZones(
+                DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+            )
         val expectedZones: List<DragZoneVerifier> =
             listOf(
                 verifyInstance<DragZone.Dismiss>(),
@@ -206,9 +258,17 @@
 
     @Test
     fun dragZonesForExpandedView_foldable_landscape() {
-        dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                foldableLandscape,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
-            dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+            dragZoneFactory.createSortedDragZones(
+                DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+            )
         val expectedZones: List<DragZoneVerifier> =
             listOf(
                 verifyInstance<DragZone.Dismiss>(),
@@ -225,7 +285,13 @@
     @Test
     fun dragZonesForBubble_tablet_desktopModeDisabled() {
         isDesktopWindowModeSupported = false
-        dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                foldableLandscape,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
             dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
         assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
@@ -234,9 +300,17 @@
     @Test
     fun dragZonesForExpandedView_tablet_desktopModeDisabled() {
         isDesktopWindowModeSupported = false
-        dragZoneFactory = DragZoneFactory(foldableLandscape, splitScreenModeChecker, desktopWindowModeChecker)
+        dragZoneFactory =
+            DragZoneFactory(
+                context,
+                foldableLandscape,
+                splitScreenModeChecker,
+                desktopWindowModeChecker
+            )
         val dragZones =
-            dragZoneFactory.createSortedDragZones(DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+            dragZoneFactory.createSortedDragZones(
+                DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+            )
         assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 55e9de5..f69bf34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -30,6 +30,7 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges
 import org.junit.Assert.assertFalse
@@ -52,10 +53,14 @@
 class DesktopModeCompatPolicyTest : ShellTestCase() {
     @get:Rule val compatRule = PlatformCompatChangeRule()
     private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+    private val packageManager: PackageManager = mock()
+    private val homeActivities = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
 
     @Before
     fun setUp() {
         desktopModeCompatPolicy = DesktopModeCompatPolicy(mContext)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+        mContext.setMockPackageManager(packageManager)
     }
 
     @Test
@@ -128,10 +133,6 @@
 
     @Test
     fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage() {
-        val packageManager: PackageManager = mock()
-        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
-        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
-        mContext.setMockPackageManager(packageManager)
         assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
             createFreeformTask(/* displayId */ 0)
                 .apply {
@@ -142,10 +143,6 @@
 
     @Test
     fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notDisplayed() {
-        val packageManager: PackageManager = mock()
-        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
-        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
-        mContext.setMockPackageManager(packageManager)
         assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
             createFreeformTask(/* displayId */ 0)
                 .apply {
@@ -155,6 +152,21 @@
     }
 
     @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notYetAvailable() {
+        val emptyHomeActivities: ComponentName = mock()
+        mContext.setMockPackageManager(packageManager)
+
+        whenever(emptyHomeActivities.packageName).thenReturn(null)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(emptyHomeActivities)
+
+        assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    isTopActivityNoDisplay = false
+                }))
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
     @DisableCompatChanges(ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
     fun testShouldExcludeCaptionFromAppBounds_resizeable_false() {
@@ -181,6 +193,17 @@
         )
     }
 
+
+    @Test
+    @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+    @DisableCompatChanges(ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+    @EnableCompatChanges(ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS)
+    fun testShouldExcludeCaptionFromAppBounds_resizeable_overridden_true() {
+        assertTrue(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(
+            setUpFreeformTask().apply { isResizeable = true })
+        )
+    }
+
     fun setUpFreeformTask(): TaskInfo =
         createFreeformTask().apply {
             val componentName =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 82392e0..18fdbef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -48,6 +48,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -96,7 +97,7 @@
         mTransitionHandler = new DefaultTransitionHandler(
                 mContext, mShellInit, mDisplayController,
                 mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor,
-                mRootTaskDisplayAreaOrganizer);
+                mRootTaskDisplayAreaOrganizer, mock(InteractionJankMonitor.class));
         mShellInit.init();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 49812d3..da41a23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -28,7 +28,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.Intent.ACTION_MAIN
-import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.graphics.Region
 import android.hardware.display.DisplayManager
@@ -310,14 +309,10 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun testDecorationIsNotCreatedForDefaultHomePackage() {
-        val packageManager: PackageManager = org.mockito.kotlin.mock()
-        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
-            baseActivity = homeActivities
+            baseActivity = homeComponentName
             isTopActivityNoDisplay = false
         }
-        mContext.setMockPackageManager(packageManager)
-        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
 
         onTaskOpening(task)
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 9f106da..e40034b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -20,7 +20,9 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WindowingMode
+import android.content.ComponentName
 import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.hardware.input.InputManager
 import android.os.Handler
@@ -146,8 +148,10 @@
     protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
     protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
     protected val motionEvent = mock<MotionEvent>()
-    val displayLayout = mock<DisplayLayout>()
-    val display = mock<Display>()
+    private val displayLayout = mock<DisplayLayout>()
+    private val display = mock<Display>()
+    private val packageManager = mock<PackageManager>()
+    protected val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
     protected lateinit var spyContext: TestableContext
     private lateinit var desktopModeEventLogger: DesktopModeEventLogger
 
@@ -178,7 +182,7 @@
         whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
         whenever(mockDesktopUserRepositories.getProfile(anyInt()))
             .thenReturn(mockDesktopRepository)
-        desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+        desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
             spyContext,
             testShellExecutor,
@@ -273,6 +277,8 @@
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
+        spyContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(homeComponentName)
     }
 
     @After
@@ -354,5 +360,6 @@
         val STABLE_INSETS = Rect(0, 100, 0, 0)
         val INITIAL_BOUNDS = Rect(0, 0, 100, 100)
         val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+        val HOME_LAUNCHER_PACKAGE_NAME = "com.android.launcher"
     }
 }
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index a958a09..36feabd 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -32,8 +32,9 @@
                                 "PerformTraversalsStart",
                                 "DrawStart",
                                 "FrameDeadline",
-                                "FrameInterval",
                                 "FrameStartTime",
+                                "FrameInterval",
+                                "WorkloadTarget",
                                 "SyncQueued",
                                 "SyncStart",
                                 "IssueDrawCommandsStart",
@@ -48,7 +49,7 @@
 
 };
 
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 23,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 24,
               "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
 
 void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f7ad139..61c30b8 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -30,7 +30,8 @@
 namespace android {
 namespace uirenderer {
 
-static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 12;
+// This value must be in sync with `FRAME_INFO_SIZE` in FrameInfo.Java
+static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 13;
 
 enum class FrameInfoIndex {
     Flags = 0,
@@ -47,6 +48,11 @@
     FrameInterval,
     // End of UI frame info
 
+    // The target workload duration based on the original frame deadline and
+    // and intended vsync. Counted in UI_THREAD_FRAME_INFO_SIZE so its value
+    // can be set in setVsync().
+    WorkloadTarget,
+
     SyncQueued,
 
     SyncStart,
@@ -109,6 +115,7 @@
         set(FrameInfoIndex::FrameStartTime) = vsyncTime;
         set(FrameInfoIndex::FrameDeadline) = frameDeadline;
         set(FrameInfoIndex::FrameInterval) = frameInterval;
+        set(FrameInfoIndex::WorkloadTarget) = frameDeadline - intendedVsync;
         return *this;
     }
 
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 638a060..80eb6bc 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -201,7 +201,7 @@
     // If we are in triple buffering, we have enough buffers in queue to sustain a single frame
     // drop without jank, so adjust the frame interval to the deadline.
     if (isTripleBuffered) {
-        int64_t originalDeadlineDuration = deadline - frame[FrameInfoIndex::IntendedVsync];
+        int64_t originalDeadlineDuration = frame[FrameInfoIndex::WorkloadTarget];
         deadline = mNextFrameStartUnstuffed + originalDeadlineDuration;
         frame.set(FrameInfoIndex::FrameDeadline) = deadline;
     }
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 7d01dfb..21430f7 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -57,6 +57,9 @@
 constexpr bool early_preload_gl_context() {
     return false;
 }
+constexpr bool calc_workload_orig_deadline() {
+    return false;
+}
 }  // namespace hwui_flags
 #endif
 
@@ -299,5 +302,10 @@
                                  hwui_flags::early_preload_gl_context());
 }
 
+bool Properties::calcWorkloadOrigDeadline() {
+    static bool sCalcWorkloadOrigDeadline = base::GetBoolProperty(
+            "debug.hwui.calc_workload_orig_deadline", hwui_flags::calc_workload_orig_deadline());
+    return sCalcWorkloadOrigDeadline;
+}
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 280a75a..a7a5644 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -384,6 +384,7 @@
     static bool initializeGlAlways();
     static bool resampleGainmapRegions();
     static bool earlyPreloadGlContext();
+    static bool calcWorkloadOrigDeadline();
 
 private:
     static StretchEffectBehavior stretchEffectBehavior;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 2851dd8..62fd7d3 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -176,4 +176,15 @@
   namespace: "core_graphics"
   description: "Initialize GL context and GraphicBufferAllocater init on renderThread preload. This improves app startup time for apps using GL."
   bug: "383612849"
+}
+
+flag {
+  name: "calc_workload_orig_deadline"
+  namespace: "window_surfaces"
+  description: "Use original frame deadline to calculate the workload target deadline for jank tracking"
+  bug: "389939827"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b36b8be..e3e393c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -789,7 +789,13 @@
     int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
     int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
 
-    mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
+    if (Properties::calcWorkloadOrigDeadline()) {
+        // Uses the unmodified frame deadline in calculating workload target duration
+        mHintSessionWrapper->updateTargetWorkDuration(
+                mCurrentFrameInfo->get(FrameInfoIndex::WorkloadTarget));
+    } else {
+        mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
+    }
 
     if (didDraw) {
         int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
diff --git a/libs/hwui/tests/unit/JankTrackerTests.cpp b/libs/hwui/tests/unit/JankTrackerTests.cpp
index b67e419..c289d67 100644
--- a/libs/hwui/tests/unit/JankTrackerTests.cpp
+++ b/libs/hwui/tests/unit/JankTrackerTests.cpp
@@ -45,6 +45,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 115_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     info = jankTracker.startFrame();
@@ -55,6 +56,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 131_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(2, container.get()->totalFrameCount());
@@ -79,6 +81,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->totalFrameCount());
@@ -102,6 +105,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 118_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->totalFrameCount());
@@ -127,6 +131,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -140,6 +145,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 137_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(2, container.get()->totalFrameCount());
@@ -164,6 +170,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 121_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 120_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -177,6 +184,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 137_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 136_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -190,6 +198,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 169_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 168_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 20_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(3, container.get()->totalFrameCount());
@@ -214,6 +223,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 117_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 116_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -228,6 +238,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 133_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 132_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(1, container.get()->jankFrameCount());
@@ -242,6 +253,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 165_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 148_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(2, container.get()->jankFrameCount());
@@ -256,6 +268,7 @@
     info->set(FrameInfoIndex::FrameCompleted) = 181_ms;
     info->set(FrameInfoIndex::FrameInterval) = 16_ms;
     info->set(FrameInfoIndex::FrameDeadline) = 164_ms;
+    info->set(FrameInfoIndex::WorkloadTarget) = 16_ms;
     jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
 
     ASSERT_EQ(2, container.get()->jankFrameCount());
diff --git a/media/java/android/media/audiofx/HapticGenerator.java b/media/java/android/media/audiofx/HapticGenerator.java
index d2523ef..7f94dde 100644
--- a/media/java/android/media/audiofx/HapticGenerator.java
+++ b/media/java/android/media/audiofx/HapticGenerator.java
@@ -36,6 +36,20 @@
  * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
  * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
  * effects.
+ *
+ * <pre>{@code
+ * AudioManager audioManager = context.getSystemService(AudioManager.class);
+ * player = MediaPlayer.create(
+ *         context,
+ *         audioUri,
+ *         new AudioAttributes.Builder().setHapticChannelsMuted(false).build(),
+ *         audioManager.generateAudioSessionId()
+ * );
+ * if (HapticGenerator.isAvailable()) {
+ *     HapticGenerator.create(player.getAudioSessionId()).setEnabled(true);
+ * }
+ * player.start();
+ * }</pre>
  */
 public class HapticGenerator extends AudioEffect implements AutoCloseable {
 
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f09dc72..6b72173 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -25,8 +25,6 @@
     min_sdk_version: "",
 
     srcs: [
-        "android_media_ImageWriter.cpp",
-        "android_media_ImageReader.cpp",
         "android_media_JetPlayer.cpp",
         "android_media_MediaCrypto.cpp",
         "android_media_MediaCodec.cpp",
@@ -42,7 +40,6 @@
         "android_media_MediaProfiles.cpp",
         "android_media_MediaRecorder.cpp",
         "android_media_MediaSync.cpp",
-        "android_media_PublicFormatUtils.cpp",
         "android_media_ResampleInputStream.cpp",
         "android_media_Streams.cpp",
         "android_media_SyncParams.cpp",
@@ -64,7 +61,6 @@
         "libbinder",
         "libmedia",
         "libmedia_codeclist",
-        "libmedia_jni_utils",
         "libmedia_omx",
         "libmediametrics",
         "libmediadrm",
@@ -133,38 +129,6 @@
 }
 
 cc_library_shared {
-    name: "libmedia_jni_utils",
-    srcs: [
-        ":libgui_frame_event_aidl",
-        "android_media_Utils.cpp",
-    ],
-
-    header_libs: [
-        "libgui_headers",
-    ],
-
-    shared_libs: [
-        "liblog",
-        "libui",
-        "libutils",
-    ],
-
-    include_dirs: [
-        "system/media/camera/include",
-    ],
-
-    export_include_dirs: ["."],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-error=deprecated-declarations",
-        "-Wunused",
-        "-Wunreachable-code",
-    ],
-}
-
-cc_library_shared {
     name: "libmedia_tv_tuner",
     min_sdk_version: "",
     srcs: [
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index a942300..647b553 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1476,8 +1476,6 @@
     return AndroidRuntime::registerNativeMethods(env,
                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));
 }
-extern int register_android_media_ImageReader(JNIEnv *env);
-extern int register_android_media_ImageWriter(JNIEnv *env);
 extern int register_android_media_JetPlayer(JNIEnv *env);
 extern int register_android_media_Crypto(JNIEnv *env);
 extern int register_android_media_Drm(JNIEnv *env);
@@ -1490,7 +1488,6 @@
 extern int register_android_media_MediaMuxer(JNIEnv *env);
 extern int register_android_media_MediaRecorder(JNIEnv *env);
 extern int register_android_media_MediaSync(JNIEnv *env);
-extern int register_android_media_PublicFormatUtils(JNIEnv *env);
 extern int register_android_media_ResampleInputStream(JNIEnv *env);
 extern int register_android_media_MediaProfiles(JNIEnv *env);
 extern int register_android_mtp_MtpDatabase(JNIEnv *env);
@@ -1508,16 +1505,6 @@
     }
     assert(env != NULL);
 
-    if (register_android_media_ImageWriter(env) != JNI_OK) {
-        ALOGE("ERROR: ImageWriter native registration failed");
-        goto bail;
-    }
-
-    if (register_android_media_ImageReader(env) < 0) {
-        ALOGE("ERROR: ImageReader native registration failed");
-        goto bail;
-    }
-
     if (register_android_media_JetPlayer(env) < 0) {
         ALOGE("ERROR: JetPlayer native registration failed");
         goto bail;
@@ -1538,11 +1525,6 @@
         goto bail;
     }
 
-    if (register_android_media_PublicFormatUtils(env) < 0) {
-        ALOGE("ERROR: PublicFormatUtils native registration failed\n");
-        goto bail;
-    }
-
     if (register_android_media_ResampleInputStream(env) < 0) {
         ALOGE("ERROR: ResampleInputStream native registration failed\n");
         goto bail;
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
index e83b9f1..d0de1fc 100644
--- a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -817,7 +817,7 @@
         if (!mOnHost && !autoTransact) {
             return;
         }
-        mAutoTransact.put(pollingLoopFilter, autoTransact);
+        mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), autoTransact);
     }
 
     /**
@@ -845,7 +845,8 @@
         if (!mOnHost && !autoTransact) {
             return;
         }
-        mAutoTransactPatterns.put(Pattern.compile(pollingLoopPatternFilter), autoTransact);
+        mAutoTransactPatterns.put(Pattern.compile(
+                pollingLoopPatternFilter.toUpperCase(Locale.ROOT)), autoTransact);
     }
 
     /**
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm
new file mode 100644
index 0000000..0059d00
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_india.kcm
@@ -0,0 +1,400 @@
+# Copyright 2025 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.
+
+#
+# English (India) keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+    ralt:                               '\u0300'
+    ralt+shift:                         '\u0303'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+    ralt, ctrl+shift:                   '\u20b9'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '^'
+    ralt+shift:                         '\u0302'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+    ralt+shift:                         '\u0306'
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+    ralt+shift:                         '\u0331'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+    ralt:                               '\u2013'
+    ralt+shift:                         '\u2014'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+    capslock+shift:                     'q'
+    ralt:                               '\u00e6'
+    ralt+shift, ralt+capslock:          '\u00c6'
+    ralt+shift+capslock:                '\u00e6'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+    capslock+shift:                     'w'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+    capslock+shift:                     'e'
+    ralt:                               '\u0113'
+    ralt+shift, ralt+capslock:          '\u0112'
+    ralt+shift+capslock:                '\u0113'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+    capslock+shift:                     'r'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+    capslock+shift:                     't'
+    ralt:                               '\u1e6d'
+    ralt+shift, ralt+capslock:          '\u1e6c'
+    ralt+shift+capslock:                '\u1e6d'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+    capslock+shift:                     'y'
+    ralt:                               '\u00f1'
+    ralt+shift, ralt+capslock:          '\u00d1'
+    ralt+shift+capslock:                '\u00f1'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+    capslock+shift:                     'u'
+    ralt:                               '\u016b'
+    ralt+shift, ralt+capslock:          '\u016a'
+    ralt+shift+capslock:                '\u016b'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+    capslock+shift:                     'i'
+    ralt:                               '\u012b'
+    ralt+shift, ralt+capslock:          '\u012a'
+    ralt+shift+capslock:                '\u012b'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+    capslock+shift:                     'o'
+    ralt:                               '\u014d'
+    ralt+shift, ralt+capslock:          '\u014c'
+    ralt+shift+capslock:                '\u014d'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+    capslock+shift:                     'p'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    base:                               '['
+    shift:                              '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    base:                               ']'
+    shift:                              '}'
+}
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+    capslock+shift:                     'a'
+    ralt:                               '\u0101'
+    ralt+shift, ralt+capslock:          '\u0100'
+    ralt+shift+capslock:                '\u0101'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+    capslock+shift:                     's'
+    ralt:                               '\u015b'
+    ralt+shift, ralt+capslock:          '\u015a'
+    ralt+shift+capslock:                '\u015b'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+    capslock+shift:                     'd'
+    ralt:                               '\u1e0d'
+    ralt+shift, ralt+capslock:          '\u1e0c'
+    ralt+shift+capslock:                 '\u1e0d'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+    capslock+shift:                     'f'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+    capslock+shift:                     'g'
+    ralt:                               '\u1e45'
+    ralt+shift, ralt+capslock:          '\u1e44'
+    ralt+shift+capslock:                '\u1e45'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+    capslock+shift:                     'h'
+    ralt:                               '\u1e25'
+    ralt+shift, ralt+capslock:          '\u1e24'
+    ralt+shift+capslock:                '\u1e25'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+    capslock+shift:                     'j'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+    capslock+shift:                     'k'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+    capslock+shift:                     'l'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift:                              ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '\u0022'
+    ralt:                               '\u030d'
+    ralt+shift:                         '\u030e'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+### ROW 4
+
+key PLUS {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+    capslock+shift:                     'z'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+    capslock+shift:                     'x'
+    ralt:                               '\u1e63'
+    ralt+shift, ralt+capslock:          '\u1e62'
+    ralt+shift+capslock:                '\u1e63'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+    capslock+shift:                     'c'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+    capslock+shift:                     'v'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+    capslock+shift:                     'b'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+    capslock+shift:                     'n'
+    ralt:                               '\u1e47'
+    ralt+shift, ralt+capslock:          '\u1e46'
+    ralt+shift+capslock:                '\u1e47'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+    capslock+shift:                     'm'
+    ralt:                               '\u1e41'
+    ralt+shift, ralt+capslock:          '\u1e40'
+    ralt+shift+capslock:                '\u1e41'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+    ralt+shift:                         '\u030C'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+    ralt:                               '\u0323'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index bd7cdc4..8a397a5 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -167,4 +167,7 @@
 
     <!-- Romanian keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_romanian">Romanian</string>
+
+    <!-- English (India) keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_english_india">English (India)</string>
 </resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 9ce9a87..fa0ed13 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -367,4 +367,11 @@
         android:keyboardLayout="@raw/keyboard_layout_romanian"
         android:keyboardLocale="ro-Latn-RO"
         android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_india"
+        android:label="@string/keyboard_layout_english_india"
+        android:keyboardLayout="@raw/keyboard_layout_english_india"
+        android:keyboardLocale="en-Latn-IN"
+        android:keyboardLayoutType="qwerty" />
 </keyboard-layouts>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java
index 95bdb09..e735610 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PreviewPageFrame.java
@@ -16,6 +16,8 @@
 
 package com.android.printspooler.widget;
 
+import static android.view.accessibility.Flags.triStateChecked;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityEvent;
@@ -38,6 +40,20 @@
     }
 
     @Override
+    public boolean performClick() {
+        final boolean result = super.performClick();
+        // This widget is incorrectly using the notion of "selection"
+        // to represent checked state. We can't send this event in
+        // setSelected() because setSelected() is called when this widget
+        // is not attached.
+        if (triStateChecked()) {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
+        }
+        return result;
+    }
+
+    @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setChecked(isSelected());
@@ -48,6 +64,11 @@
         super.onInitializeAccessibilityNodeInfo(info);
         info.setSelected(false);
         info.setCheckable(true);
-        info.setChecked(isSelected());
+        if (triStateChecked()) {
+            info.setChecked(isSelected() ? AccessibilityNodeInfo.CHECKED_STATE_TRUE :
+                    AccessibilityNodeInfo.CHECKED_STATE_FALSE);
+        } else {
+            info.setChecked(isSelected());
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index bf86911..572444e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -30,11 +30,13 @@
 import androidx.annotation.ChecksSdkIntAtLeast;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -385,7 +387,7 @@
                 preferredMainDevice.refresh();
                 hasChanged = true;
             }
-            syncAudioSharingSourceIfNeeded(preferredMainDevice);
+            syncAudioSharingStatusIfNeeded(preferredMainDevice);
         }
         if (hasChanged) {
             log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -399,13 +401,16 @@
         return userManager != null && userManager.isManagedProfile();
     }
 
-    private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
+    private void syncAudioSharingStatusIfNeeded(CachedBluetoothDevice mainDevice) {
         boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
-        if (isAudioSharingEnabled) {
+        if (isAudioSharingEnabled && mainDevice != null) {
             if (isWorkProfile()) {
-                log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+                log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, work profile");
                 return;
             }
+            Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
+            deviceSet.add(mainDevice);
+            deviceSet.addAll(mainDevice.getMemberDevice());
             boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
                     && BluetoothUtils.hasConnectedBroadcastSource(
                     mainDevice, mBtManager);
@@ -419,9 +424,6 @@
                 if (metadata != null && assistant != null) {
                     log("addMemberDevicesIntoMainDevice: sync audio sharing source after "
                             + "combining the top level devices.");
-                    Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
-                    deviceSet.add(mainDevice);
-                    deviceSet.addAll(mainDevice.getMemberDevice());
                     Set<BluetoothDevice> sinksToSync = deviceSet.stream()
                             .map(CachedBluetoothDevice::getDevice)
                             .filter(device ->
@@ -435,8 +437,24 @@
                     }
                 }
             }
+            if (Flags.enableTemporaryBondDevicesUi()) {
+                log("addMemberDevicesIntoMainDevice: sync temp bond metadata for audio sharing "
+                        + "sinks after combining the top level devices.");
+                Set<BluetoothDevice> sinksToSync = deviceSet.stream()
+                        .map(CachedBluetoothDevice::getDevice).filter(Objects::nonNull).collect(
+                                Collectors.toSet());
+                if (sinksToSync.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice)) {
+                    for (BluetoothDevice device : sinksToSync) {
+                        if (!BluetoothUtils.isTemporaryBondDevice(device)) {
+                            log("addMemberDevicesIntoMainDevice: sync temp bond metadata for "
+                                    + device.getAnonymizedAddress());
+                            BluetoothUtils.setTemporaryBondMetadata(device);
+                        }
+                    }
+                }
+            }
         } else {
-            log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
+            log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, flag disabled");
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
index 10156c4..bac564c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager
 import android.media.MediaMetadata
 import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
 import android.media.session.MediaSession
 import android.media.session.MediaSessionManager
 import android.media.session.PlaybackState
@@ -98,16 +99,22 @@
     }
 
     /** Set volume `level` to remote media `token` */
-    fun setVolume(token: MediaSession.Token, level: Int) {
+    fun setVolume(sessionId: SessionId, volumeLevel: Int) {
+        when (sessionId) {
+            is SessionId.Media -> setMediaSessionVolume(sessionId.token, volumeLevel)
+        }
+    }
+
+    private fun setMediaSessionVolume(token: MediaSession.Token, volumeLevel: Int) {
         val record = mRecords[token]
         if (record == null) {
             Log.w(TAG, "setVolume: No record found for token $token")
             return
         }
         if (D.BUG) {
-            Log.d(TAG, "Setting level to $level")
+            Log.d(TAG, "Setting level to $volumeLevel")
         }
-        record.controller.setVolumeTo(level, 0)
+        record.controller.setVolumeTo(volumeLevel, 0)
     }
 
     private fun onRemoteVolumeChangedH(sessionToken: MediaSession.Token, flags: Int) {
@@ -122,7 +129,7 @@
             )
         }
         val token = controller.sessionToken
-        mCallbacks.onRemoteVolumeChanged(token, flags)
+        mCallbacks.onRemoteVolumeChanged(SessionId.from(token), flags)
     }
 
     private fun onUpdateRemoteSessionListH(sessionToken: MediaSession.Token?) {
@@ -158,7 +165,7 @@
                 controller.registerCallback(record, mHandler)
             }
             val record = mRecords[token]
-            val remote = isRemote(playbackInfo)
+            val remote = playbackInfo.isRemote()
             if (remote) {
                 updateRemoteH(token, record!!.name, playbackInfo)
                 record.sentRemote = true
@@ -172,7 +179,7 @@
                 Log.d(TAG, "Removing " + record.name + " sentRemote=" + record.sentRemote)
             }
             if (record.sentRemote) {
-                mCallbacks.onRemoteRemoved(token)
+                mCallbacks.onRemoteRemoved(SessionId.from(token))
                 record.sentRemote = false
             }
         }
@@ -213,8 +220,8 @@
     private fun updateRemoteH(
         token: MediaSession.Token,
         name: String?,
-        pi: MediaController.PlaybackInfo,
-    ) = mCallbacks.onRemoteUpdate(token, name, pi)
+        playbackInfo: PlaybackInfo,
+    ) = mCallbacks.onRemoteUpdate(SessionId.from(token), name, VolumeInfo.from(playbackInfo))
 
     private inner class MediaControllerRecord(val controller: MediaController) :
         MediaController.Callback() {
@@ -225,7 +232,7 @@
             return method + " " + controller.packageName + " "
         }
 
-        override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
+        override fun onAudioInfoChanged(info: PlaybackInfo) {
             if (D.BUG) {
                 Log.d(
                     TAG,
@@ -235,9 +242,9 @@
                         sentRemote),
                 )
             }
-            val remote = isRemote(info)
+            val remote = info.isRemote()
             if (!remote && sentRemote) {
-                mCallbacks.onRemoteRemoved(controller.sessionToken)
+                mCallbacks.onRemoteRemoved(SessionId.from(controller.sessionToken))
                 sentRemote = false
             } else if (remote) {
                 updateRemoteH(controller.sessionToken, name, info)
@@ -301,20 +308,36 @@
         }
     }
 
+    /** Opaque id for ongoing sessions that support volume adjustment. */
+    sealed interface SessionId {
+
+        companion object {
+            fun from(token: MediaSession.Token) = Media(token)
+        }
+
+        data class Media(val token: MediaSession.Token) : SessionId
+    }
+
+    /** Holds session volume information. */
+    data class VolumeInfo(val currentVolume: Int, val maxVolume: Int) {
+
+        companion object {
+
+            fun from(playbackInfo: PlaybackInfo) =
+                VolumeInfo(playbackInfo.currentVolume, playbackInfo.maxVolume)
+        }
+    }
+
     /** Callback for remote media sessions */
     interface Callbacks {
         /** Invoked when remote media session is updated */
-        fun onRemoteUpdate(
-            token: MediaSession.Token?,
-            name: String?,
-            pi: MediaController.PlaybackInfo?,
-        )
+        fun onRemoteUpdate(token: SessionId?, name: String?, volumeInfo: VolumeInfo?)
 
         /** Invoked when remote media session is removed */
-        fun onRemoteRemoved(token: MediaSession.Token?)
+        fun onRemoteRemoved(token: SessionId?)
 
         /** Invoked when remote volume is changed */
-        fun onRemoteVolumeChanged(token: MediaSession.Token?, flags: Int)
+        fun onRemoteVolumeChanged(token: SessionId?, flags: Int)
     }
 
     companion object {
@@ -325,12 +348,11 @@
         const val UPDATE_REMOTE_SESSION_LIST: Int = 3
 
         private const val USE_SERVICE_LABEL = false
-
-        private fun isRemote(pi: MediaController.PlaybackInfo?): Boolean =
-            pi != null && pi.playbackType == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
     }
 }
 
+private fun PlaybackInfo?.isRemote() = this?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+
 private fun MediaController.dump(n: Int, writer: PrintWriter) {
     writer.println("  Controller $n: $packageName")
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
new file mode 100644
index 0000000..bba278a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/display/DisplayDensityUtilsTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2025 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.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class DisplayDensityUtilsTest {
+
+    private static final float MAX_SCALE = 1.33f;
+    private static final float MIN_SCALE = 0.85f;
+    private static final float MIN_INTERVAL = 0.09f;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private DisplayManager mDisplayManager;
+    @Mock
+    private DisplayManagerGlobal mDisplayManagerGlobal;
+    @Mock
+    private IWindowManager mIWindowManager;
+    private IWindowManager mWindowManagerToRestore;
+    private DisplayDensityUtils mDisplayDensityUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mDisplayManager).when(mContext).getSystemService((Class<Object>) any());
+        mWindowManagerToRestore = WindowManagerGlobal.getWindowManagerService();
+        WindowManagerGlobal.setWindowManagerServiceForSystemProcess(mIWindowManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.display_density_max_scale, 1, 1)).thenReturn(
+                MAX_SCALE);
+        when(mResources.getFraction(R.fraction.display_density_min_scale, 1, 1)).thenReturn(
+                MIN_SCALE);
+        when(mResources.getFraction(R.fraction.display_density_min_scale_interval, 1,
+                1)).thenReturn(MIN_INTERVAL);
+        when(mResources.getString(anyInt())).thenReturn("summary");
+    }
+
+    @After
+    public void teardown() {
+        WindowManagerGlobal.setWindowManagerServiceForSystemProcess(mWindowManagerToRestore);
+    }
+
+    @Test
+    public void createDisplayDensityUtil_onlyDefaultDisplay() throws RemoteException {
+        var info = createDisplayInfoForDisplay(Display.DEFAULT_DISPLAY, Display.TYPE_INTERNAL, 2560,
+                1600, 320);
+        var display = new Display(mDisplayManagerGlobal, info.displayId, info,
+                (DisplayAdjustments) null);
+        doReturn(new Display[]{display}).when(mDisplayManager).getDisplays(any());
+        doReturn(display).when(mDisplayManager).getDisplay(info.displayId);
+
+        mDisplayDensityUtils = new DisplayDensityUtils(mContext);
+
+        assertThat(mDisplayDensityUtils.getValues()).isEqualTo(new int[]{272, 320, 354, 390, 424});
+    }
+
+    @Test
+    public void createDisplayDensityUtil_multipleInternalDisplays() throws RemoteException {
+        // Default display
+        var defaultDisplayInfo = createDisplayInfoForDisplay(Display.DEFAULT_DISPLAY,
+                Display.TYPE_INTERNAL, 2000, 2000, 390);
+        var defaultDisplay = new Display(mDisplayManagerGlobal, defaultDisplayInfo.displayId,
+                defaultDisplayInfo,
+                (DisplayAdjustments) null);
+        doReturn(defaultDisplay).when(mDisplayManager).getDisplay(defaultDisplayInfo.displayId);
+
+        // Create another internal display
+        var internalDisplayInfo = createDisplayInfoForDisplay(1, Display.TYPE_INTERNAL,
+                2000, 1000, 390);
+        var internalDisplay = new Display(mDisplayManagerGlobal, internalDisplayInfo.displayId,
+                internalDisplayInfo,
+                (DisplayAdjustments) null);
+        doReturn(internalDisplay).when(mDisplayManager).getDisplay(internalDisplayInfo.displayId);
+
+        doReturn(new Display[]{defaultDisplay, internalDisplay}).when(mDisplayManager).getDisplays(
+                anyString());
+
+        mDisplayDensityUtils = new DisplayDensityUtils(mContext);
+
+        assertThat(mDisplayDensityUtils.getValues()).isEqualTo(new int[]{330, 390, 426, 462, 500});
+    }
+
+    private DisplayInfo createDisplayInfoForDisplay(int displayId, int displayType,
+            int width, int height, int density) throws RemoteException {
+        var displayInfo = new DisplayInfo();
+        displayInfo.displayId = displayId;
+        displayInfo.type = displayType;
+        displayInfo.logicalWidth = width;
+        displayInfo.logicalHeight = height;
+        displayInfo.logicalDensityDpi = density;
+
+        doReturn(displayInfo).when(mDisplayManagerGlobal).getDisplayInfo(displayInfo.displayId);
+        doReturn(displayInfo.logicalDensityDpi).when(mIWindowManager).getInitialDisplayDensity(
+                displayInfo.displayId);
+        return displayInfo;
+    }
+}
+
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index fd14d1f..2eccaa6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -40,6 +40,8 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import com.android.settingslib.flags.Flags;
@@ -74,6 +76,9 @@
     private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
     private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
     private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
+    private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+    private static final String TEMP_BOND_METADATA =
+            "<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
     private final static int GROUP1 = 1;
     private final BluetoothClass DEVICE_CLASS_1 =
             createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
@@ -337,6 +342,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
     public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
         // Condition: The preferredDevice is main and there is another main device in top list
         // Expected Result: return true and there is the preferredDevice in top list
@@ -346,7 +352,6 @@
         mCachedDevices.add(preferredDevice);
         mCachedDevices.add(mCachedDevice2);
         mCachedDevices.add(mCachedDevice3);
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
 
         assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
                 .isTrue();
@@ -359,6 +364,7 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
     public void
             addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_workProfile_doNothing() {
         // Condition: The preferredDevice is main and there is another main device in top list
@@ -369,7 +375,6 @@
         mCachedDevices.add(preferredDevice);
         mCachedDevices.add(mCachedDevice2);
         mCachedDevices.add(mCachedDevice3);
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         when(mBroadcast.isEnabled(null)).thenReturn(true);
         BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -377,6 +382,8 @@
                 BluetoothLeBroadcastReceiveState.class);
         when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
         when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+        when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+                .thenReturn(TEMP_BOND_METADATA.getBytes());
         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         when(mUserManager.isManagedProfile()).thenReturn(true);
 
@@ -387,10 +394,13 @@
         assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
         assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
         verify(mAssistant, never()).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+        verify(mDevice1, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                TEMP_BOND_METADATA.getBytes());
     }
 
     @Test
-    public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
+    @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+    public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncState() {
         // Condition: The preferredDevice is main and there is another main device in top list
         // Expected Result: return true and there is the preferredDevice in top list
         CachedBluetoothDevice preferredDevice = mCachedDevice1;
@@ -399,7 +409,6 @@
         mCachedDevices.add(preferredDevice);
         mCachedDevices.add(mCachedDevice2);
         mCachedDevices.add(mCachedDevice3);
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         when(mBroadcast.isEnabled(null)).thenReturn(true);
         BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -407,6 +416,8 @@
                 BluetoothLeBroadcastReceiveState.class);
         when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
         when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+        when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+                .thenReturn(TEMP_BOND_METADATA.getBytes());
 
         assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
                 .isTrue();
@@ -415,6 +426,8 @@
         assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
         assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
         verify(mAssistant).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+        verify(mDevice1).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                TEMP_BOND_METADATA.getBytes());
     }
 
     @Test
@@ -436,13 +449,13 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
     public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
         // Condition: The preferredDevice is member and there are two main device in top list
         // Expected Result: return true and there is the preferredDevice in top list
         CachedBluetoothDevice preferredDevice = mCachedDevice2;
         BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
         mCachedDevice3.setGroupId(GROUP1);
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         when(mBroadcast.isEnabled(null)).thenReturn(false);
 
         assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
@@ -457,16 +470,20 @@
         assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
         verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
                 any(BluetoothLeBroadcastMetadata.class), anyBoolean());
+        verify(mDevice2, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                TEMP_BOND_METADATA.getBytes());
+        verify(mDevice3, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                TEMP_BOND_METADATA.getBytes());
     }
 
     @Test
-    public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncSource() {
+    @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+    public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncState() {
         // Condition: The preferredDevice is member and there are two main device in top list
         // Expected Result: return true and there is the preferredDevice in top list
         CachedBluetoothDevice preferredDevice = mCachedDevice2;
         BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
         mCachedDevice3.setGroupId(GROUP1);
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         when(mBroadcast.isEnabled(null)).thenReturn(true);
         BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -474,6 +491,8 @@
                 BluetoothLeBroadcastReceiveState.class);
         when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
         when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
+        when(mDevice1.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+                .thenReturn(TEMP_BOND_METADATA.getBytes());
 
         assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
                 .isTrue();
@@ -488,6 +507,10 @@
         assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
         verify(mAssistant).addSource(mDevice2, metadata, /* isGroupOp= */ false);
         verify(mAssistant).addSource(mDevice3, metadata, /* isGroupOp= */ false);
+        verify(mDevice2).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                TEMP_BOND_METADATA.getBytes());
+        verify(mDevice3).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                TEMP_BOND_METADATA.getBytes());
     }
 
     @Test
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 246aa71..85617ba 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -809,7 +809,9 @@
                  Settings.Secure.DND_CONFIGS_MIGRATED,
                  Settings.Secure.NAVIGATION_MODE_RESTORE,
                  Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
-                 Settings.Secure.V_TO_U_RESTORE_DENYLIST);
+                 Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+                 Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
+                 Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
index 93690d4..b0fd925 100644
--- a/packages/Shell/src/com/android/shell/BugreportPrefs.java
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -23,25 +23,24 @@
  * Preferences related to bug reports.
  */
 final class BugreportPrefs {
-    static final String PREFS_BUGREPORT = "bugreports";
-
-    private static final String KEY_WARNING_STATE = "warning-state";
-
-    static final int STATE_UNKNOWN = 0;
-    // Shows the warning dialog.
-    static final int STATE_SHOW = 1;
-    // Skips the warning dialog.
-    static final int STATE_HIDE = 2;
 
     static int getWarningState(Context context, int def) {
-        final SharedPreferences prefs = context.getSharedPreferences(
-                PREFS_BUGREPORT, Context.MODE_PRIVATE);
-        return prefs.getInt(KEY_WARNING_STATE, def);
+        String prefsBugreport = context.getResources().getString(
+                com.android.internal.R.string.prefs_bugreport);
+        String keyWarningState = context.getResources().getString(
+                com.android.internal.R.string.key_warning_state);
+        final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+                Context.MODE_PRIVATE);
+        return prefs.getInt(keyWarningState, def);
     }
 
     static void setWarningState(Context context, int value) {
-        final SharedPreferences prefs = context.getSharedPreferences(
-                PREFS_BUGREPORT, Context.MODE_PRIVATE);
-        prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+        String prefsBugreport = context.getResources().getString(
+                com.android.internal.R.string.prefs_bugreport);
+        String keyWarningState = context.getResources().getString(
+                com.android.internal.R.string.key_warning_state);
+        final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+                Context.MODE_PRIVATE);
+        prefs.edit().putInt(keyWarningState, value).apply();
     }
 }
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 61f49db..fb0678f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,8 +21,6 @@
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
 import static com.android.shell.BugreportPrefs.getWarningState;
 import static com.android.shell.flags.Flags.handleBugreportsForWear;
 
@@ -1347,7 +1345,11 @@
     }
 
     private boolean hasUserDecidedNotToGetWarningMessage() {
-        return getWarningState(mContext, STATE_UNKNOWN) == STATE_HIDE;
+        int bugreportStateUnknown = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_unknown);
+        int bugreportStateHide = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_hide);
+        return getWarningState(mContext, bugreportStateUnknown) == bugreportStateHide;
     }
 
     private void maybeShowWarningMessageAndCloseNotification(int id) {
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
index a44e236..0e835f9 100644
--- a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -16,9 +16,6 @@
 
 package com.android.shell;
 
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
 import static com.android.shell.BugreportPrefs.getWarningState;
 import static com.android.shell.BugreportPrefs.setWarningState;
 import static com.android.shell.BugreportProgressService.sendShareIntent;
@@ -69,12 +66,19 @@
 
         mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
 
-        final int state = getWarningState(this, STATE_UNKNOWN);
+        int bugreportStateUnknown = getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_unknown);
+        int bugreportStateHide = getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_hide);
+        int bugreportStateShow = getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_show);
+
+        final int state = getWarningState(this, bugreportStateUnknown);
         final boolean checked;
         if (Build.IS_USER) {
-            checked = state == STATE_HIDE; // Only checks if specifically set to.
+            checked = state == bugreportStateHide; // Only checks if specifically set to.
         } else {
-            checked = state != STATE_SHOW; // Checks by default.
+            checked = state != bugreportStateShow; // Checks by default.
         }
         mConfirmRepeat.setChecked(checked);
 
@@ -83,9 +87,14 @@
 
     @Override
     public void onClick(DialogInterface dialog, int which) {
+        int bugreportStateHide = getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_hide);
+        int bugreportStateShow = getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_show);
         if (which == AlertDialog.BUTTON_POSITIVE) {
             // Remember confirm state, and launch target
-            setWarningState(this, mConfirmRepeat.isChecked() ? STATE_HIDE : STATE_SHOW);
+            setWarningState(this, mConfirmRepeat.isChecked() ? bugreportStateHide
+                    : bugreportStateShow);
             if (mSendIntent != null) {
                 sendShareIntent(this, mSendIntent);
             }
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 7bda2ea..2d6abe6 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,10 +19,6 @@
 import static android.test.MoreAsserts.assertContainsRegex;
 
 import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
-import static com.android.shell.BugreportPrefs.PREFS_BUGREPORT;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
 import static com.android.shell.BugreportPrefs.getWarningState;
 import static com.android.shell.BugreportPrefs.setWarningState;
 import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_REQUESTED;
@@ -201,8 +197,9 @@
             return null;
         }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
                 any(), anyBoolean(), anyBoolean());
-
-        setWarningState(mContext, STATE_HIDE);
+        int bugreportStateHide = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_hide);
+        setWarningState(mContext, bugreportStateHide);
 
         mUiBot.turnScreenOn();
     }
@@ -469,22 +466,31 @@
 
     @Test
     public void testBugreportFinished_withWarningUnknownState() throws Exception {
-        bugreportFinishedWithWarningTest(STATE_UNKNOWN);
+        int bugreportStateUnknown = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_unknown);
+        bugreportFinishedWithWarningTest(bugreportStateUnknown);
     }
 
     @Test
     public void testBugreportFinished_withWarningShowAgain() throws Exception {
-        bugreportFinishedWithWarningTest(STATE_SHOW);
+        int bugreportStateShow = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_show);
+        bugreportFinishedWithWarningTest(bugreportStateShow);
     }
 
     private void bugreportFinishedWithWarningTest(Integer propertyState) throws Exception {
+        int bugreportStateUnknown = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_unknown);
+        int bugreportStateHide = mContext.getResources().getInteger(
+                com.android.internal.R.integer.bugreport_state_hide);
         if (propertyState == null) {
             // Clear properties
-            mContext.getSharedPreferences(PREFS_BUGREPORT, Context.MODE_PRIVATE)
-                    .edit().clear().commit();
+            mContext.getSharedPreferences(
+                    mContext.getResources().getString(com.android.internal.R.string.prefs_bugreport)
+                            , Context.MODE_PRIVATE).edit().clear().commit();
             // Confidence check...
-            assertEquals("Did not reset properties", STATE_UNKNOWN,
-                    getWarningState(mContext, STATE_UNKNOWN));
+            assertEquals("Did not reset properties", bugreportStateUnknown,
+                    getWarningState(mContext, bugreportStateUnknown));
         } else {
             setWarningState(mContext, propertyState);
         }
@@ -501,7 +507,8 @@
         // TODO: get ok and dontShowAgain from the dialog reference above
         UiObject dontShowAgain =
                 mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm_dont_repeat));
-        final boolean firstTime = propertyState == null || propertyState == STATE_UNKNOWN;
+        final boolean firstTime =
+                propertyState == null || propertyState == bugreportStateUnknown;
         if (firstTime) {
             if (Build.IS_USER) {
                 assertFalse("Checkbox should NOT be checked by default on user builds",
@@ -524,8 +531,8 @@
         assertActionSendMultiple(extras);
 
         // Make sure it's hidden now.
-        int newState = getWarningState(mContext, STATE_UNKNOWN);
-        assertEquals("Didn't change state", STATE_HIDE, newState);
+        int newState = getWarningState(mContext, bugreportStateUnknown);
+        assertEquals("Didn't change state", bugreportStateHide, newState);
     }
 
     @Test
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index fad8ae7..2f38dc2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,6 +24,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.UserAction
@@ -102,6 +103,8 @@
             viewModel,
             dialogFactory,
             Modifier.element(Bouncer.Elements.Content)
+                // TODO(b/393516240): Use the same sysuiResTag() as views instead.
+                .testTag(Bouncer.Elements.Content.testTag)
                 .overscroll(verticalOverscrollEffect)
                 .sysuiResTag(Bouncer.TestTags.Root)
                 .fillMaxSize(),
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 2ca7055..0b17a3f 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
@@ -200,8 +200,9 @@
         scene(
             CommunalScenes.Blank,
             userActions =
-                if (viewModel.v2FlagEnabled()) emptyMap()
-                else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
+                if (viewModel.swipeToHubEnabled())
+                    mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal)
+                else emptyMap(),
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
             Box(modifier = Modifier.fillMaxSize())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9c57efc..835dd7a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -76,7 +76,7 @@
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.AutoSize
+import androidx.compose.foundation.text.TextAutoSize
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
@@ -1045,7 +1045,7 @@
                         color = colors.onPrimary,
                         textAlign = TextAlign.Center,
                     ),
-                autoSize = AutoSize.StepBased(maxFontSize = 36.sp, stepSize = 0.1.sp),
+                autoSize = TextAutoSize.StepBased(maxFontSize = 36.sp, stepSize = 0.1.sp),
                 modifier =
                     Modifier.focusable().semantics(mergeDescendants = true) {
                         contentDescription = titleForEmptyStateCTA
@@ -1705,15 +1705,38 @@
     contentScope: ContentScope?,
     modifier: Modifier = Modifier,
 ) {
-    if (SceneContainerFlag.isEnabled && contentScope != null) {
-        contentScope.MediaCarousel(
-            modifier = modifier.fillMaxSize(),
-            isVisible = true,
-            mediaHost = viewModel.mediaHost,
-            carouselController = viewModel.mediaCarouselController,
-        )
-    } else {
-        UmoLegacy(viewModel, modifier)
+    val showNextActionLabel = stringResource(R.string.accessibility_action_label_umo_show_next)
+    val showPreviousActionLabel =
+        stringResource(R.string.accessibility_action_label_umo_show_previous)
+
+    Box(
+        modifier =
+            modifier.thenIf(!viewModel.isEditMode) {
+                Modifier.semantics {
+                    customActions =
+                        listOf(
+                            CustomAccessibilityAction(showNextActionLabel) {
+                                viewModel.onShowNextMedia()
+                                true
+                            },
+                            CustomAccessibilityAction(showPreviousActionLabel) {
+                                viewModel.onShowPreviousMedia()
+                                true
+                            },
+                        )
+                }
+            }
+    ) {
+        if (SceneContainerFlag.isEnabled && contentScope != null) {
+            contentScope.MediaCarousel(
+                modifier = modifier.fillMaxSize(),
+                isVisible = true,
+                mediaHost = viewModel.mediaHost,
+                carouselController = viewModel.mediaCarouselController,
+            )
+        } else {
+            UmoLegacy(viewModel, modifier)
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 16002bc..8ad96a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -57,7 +57,8 @@
 internal constructor(
     communalContent: List<CommunalContentModel>,
     private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
-    private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
+    private val onDeleteWidget:
+        (id: Int, key: String, componentName: ComponentName, rank: Int) -> Unit,
     private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
     private val onResizeWidget:
         (
@@ -81,7 +82,7 @@
         if (list[indexToRemove].isWidgetContent()) {
             val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
             list.apply { removeAt(indexToRemove) }
-            onDeleteWidget(widget.appWidgetId, widget.componentName, widget.rank)
+            onDeleteWidget(widget.appWidgetId, widget.key, widget.componentName, widget.rank)
         }
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
index 62aa31b..73a2425 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
@@ -50,7 +50,6 @@
 import androidx.window.layout.WindowMetricsCalculator
 import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_HEIGHT
 import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_WIDTH
-import com.android.systemui.communal.util.WindowSizeUtils.MEDIUM_WIDTH
 
 /**
  * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
@@ -267,9 +266,8 @@
 }
 
 private fun calculateNumCellsWidth(width: Dp) =
-    // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
     when {
-        width >= MEDIUM_WIDTH -> 3
+        width >= 900.dp -> 3
         width >= COMPACT_WIDTH -> 2
         else -> 1
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 5e61af6..aa07370 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
@@ -55,7 +56,11 @@
 
     @Composable
     override fun ContentScope.Content(modifier: Modifier) {
-        LockscreenScene(lockscreenContent = lockscreenContent, modifier = modifier)
+        LockscreenScene(
+            lockscreenContent = lockscreenContent,
+            // TODO(b/393516240): Use the same sysuiResTag() as views instead.
+            modifier = modifier.testTag(key.rootElementKey.testTag),
+        )
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index aa0d474..0c502e6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -109,6 +109,13 @@
         rememberMutableSceneTransitionLayoutState(
             initialScene = initialSceneKey,
             canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+            canShowOverlay = { overlay -> viewModel.canShowOrReplaceOverlay(overlay) },
+            canReplaceOverlay = { beingReplaced, newlyShown ->
+                viewModel.canShowOrReplaceOverlay(
+                    newlyShown = newlyShown,
+                    beingReplaced = beingReplaced,
+                )
+            },
             transitions = sceneTransitions,
             onTransitionStart = { transition ->
                 sceneJankMonitor.onTransitionStart(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 907b5bc..05958a2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -169,7 +169,7 @@
             Modifier.maybeElevateInContent(layoutImpl, content, key, currentTransitionStates)
         }
         .then(ElementModifier(layoutImpl, currentTransitionStates, content, key))
-        .testTag(key.testTag)
+        .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
 }
 
 /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 72bb82b..d47210c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -65,6 +65,8 @@
     swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
+    // TODO(b/240432457) Remove this once test utils can access the internal STLForTesting().
+    implicitTestTags: Boolean = false,
     builder: SceneTransitionLayoutScope<ContentScope>.() -> Unit,
 ) {
     SceneTransitionLayoutForTesting(
@@ -73,6 +75,7 @@
         swipeSourceDetector,
         swipeDetector,
         transitionInterceptionThreshold,
+        implicitTestTags = implicitTestTags,
         onLayoutImpl = null,
         builder = builder,
     )
@@ -725,10 +728,8 @@
 }
 
 /**
- * An internal version of [SceneTransitionLayout] to be used for tests.
- *
- * Important: You should use this only in tests and if you need to access the underlying
- * [SceneTransitionLayoutImpl]. In other cases, you should use [SceneTransitionLayout].
+ * An internal version of [SceneTransitionLayout] to be used for tests, that provides access to the
+ * internal [SceneTransitionLayoutImpl] and implicitly tags all scenes and elements.
  */
 @Composable
 internal fun SceneTransitionLayoutForTesting(
@@ -741,6 +742,7 @@
     sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() },
     ancestors: List<Ancestor> = remember { emptyList() },
     lookaheadScope: LookaheadScope? = null,
+    implicitTestTags: Boolean = true,
     builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit,
 ) {
     val density = LocalDensity.current
@@ -765,6 +767,7 @@
                 directionChangeSlop = directionChangeSlop,
                 defaultEffectFactory = defaultEffectFactory,
                 decayAnimationSpec = decayAnimationSpec,
+                implicitTestTags = implicitTestTags,
             )
             .also { onLayoutImpl?.invoke(it) }
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 53996d2..e3c4eb0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -122,6 +122,9 @@
      * This is used to enable transformations and shared elements across NestedSTLs.
      */
     internal val ancestors: List<Ancestor> = emptyList(),
+
+    /** Whether elements and scene should be tagged using `Modifier.testTag`. */
+    internal val implicitTestTags: Boolean = false,
     lookaheadScope: LookaheadScope? = null,
     defaultEffectFactory: OverscrollFactory,
 ) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 95d6440..64cfe38 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -171,7 +171,7 @@
                 .thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) {
                     Modifier.container(containerState)
                 }
-                .testTag(key.testTag)
+                .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
         ) {
             CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) {
                 scope.content()
@@ -290,6 +290,7 @@
             sharedElementMap = layoutImpl.elements,
             ancestors = ancestors,
             lookaheadScope = layoutImpl.lookaheadScope,
+            implicitTestTags = layoutImpl.implicitTestTags,
         )
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 698a808..8fce708 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -227,7 +227,7 @@
             to = SceneB,
             transitionLayout = { state ->
                 coroutineScope = rememberCoroutineScope()
-                SceneTransitionLayout(state) {
+                SceneTransitionLayoutForTesting(state) {
                     scene(SceneA) {
                         Box(Modifier.size(layoutSize)) {
                             // Transformed element
@@ -633,7 +633,7 @@
 
         val scope =
             rule.setContentAndCreateMainScope {
-                SceneTransitionLayout(state) {
+                SceneTransitionLayoutForTesting(state) {
                     scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
                     scene(SceneB) {}
                 }
@@ -674,7 +674,7 @@
             CompositionLocalProvider(
                 LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
             ) {
-                SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+                SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
                     scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
                         Spacer(Modifier.fillMaxSize())
                     }
@@ -734,7 +734,7 @@
             CompositionLocalProvider(
                 LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
             ) {
-                SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+                SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
                     scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
                         Spacer(
                             Modifier.overscroll(verticalOverscrollEffect)
@@ -834,7 +834,7 @@
             CompositionLocalProvider(
                 LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
             ) {
-                SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+                SceneTransitionLayoutForTesting(state, Modifier.size(layoutWidth, layoutHeight)) {
                     scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
                         Spacer(Modifier.fillMaxSize())
                     }
@@ -893,7 +893,7 @@
             CompositionLocalProvider(
                 LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
             ) {
-                SceneTransitionLayout(
+                SceneTransitionLayoutForTesting(
                     state = state,
                     modifier = Modifier.size(layoutWidth, layoutHeight),
                 ) {
@@ -970,7 +970,7 @@
 
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
-            SceneTransitionLayout(
+            SceneTransitionLayoutForTesting(
                 state = state,
                 modifier = Modifier.size(layoutWidth, layoutHeight),
             ) {
@@ -1057,7 +1057,7 @@
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
 
-            SceneTransitionLayout(state) {
+            SceneTransitionLayoutForTesting(state) {
                 scene(SceneA) {
                     Box(Modifier.size(layoutSize)) {
                         Box(
@@ -1374,7 +1374,7 @@
 
         val scope =
             rule.setContentAndCreateMainScope {
-                SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+                SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
                     scene(SceneA) {
                         Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
                     }
@@ -1742,7 +1742,7 @@
 
         val scope =
             rule.setContentAndCreateMainScope {
-                SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
                     scene(SceneA) { Foo(offset = 0.dp) }
                     scene(SceneB) { Foo(offset = 20.dp) }
                     scene(SceneC) { Foo(offset = 40.dp) }
@@ -1828,7 +1828,7 @@
 
         val scope =
             rule.setContentAndCreateMainScope {
-                SceneTransitionLayout(state) {
+                SceneTransitionLayoutForTesting(state) {
                     scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
 
                     // Define A after B so that Foo is placed in A during A <=> B.
@@ -1887,7 +1887,7 @@
 
         val scope =
             rule.setContentAndCreateMainScope {
-                SceneTransitionLayout(state) {
+                SceneTransitionLayoutForTesting(state) {
                     scene(SceneA) { Foo() }
                     scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
                 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 04c762f..98ecb64 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -90,7 +90,7 @@
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+            SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
                 overlay(OverlayA) { Foo() }
             }
@@ -132,7 +132,7 @@
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+            SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
                 overlay(OverlayA) { Foo() }
                 overlay(OverlayB) { Foo() }
@@ -230,7 +230,7 @@
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+            SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) { MovableBar() } }
                 overlay(OverlayA) { MovableBar() }
                 overlay(OverlayB) { MovableBar() }
@@ -302,7 +302,7 @@
             }
         var alignment by mutableStateOf(Alignment.Center)
         rule.setContent {
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+            SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
                 overlay(OverlayA, alignment = alignment) { Foo() }
             }
@@ -761,7 +761,7 @@
         val movableElementChildTag = "movableElementChildTag"
         val scope =
             rule.setContentAndCreateMainScope {
-                SceneTransitionLayout(state) {
+                SceneTransitionLayoutForTesting(state) {
                     scene(SceneA) {
                         MovableElement(key, Modifier) {
                             content { Box(Modifier.testTag(movableElementChildTag).size(100.dp)) }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 2bf2358..366b11d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -250,7 +250,7 @@
             }
 
         rule.setContent {
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+            SceneTransitionLayoutForTesting(state, Modifier.size(200.dp)) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) }
                 overlay(OverlayA) { Box(Modifier.fillMaxSize()) }
                 overlay(OverlayB) { Box(Modifier.fillMaxSize()) }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 3578be4..5cbc98f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -97,7 +97,7 @@
             MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
         }
 
-        SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
+        SceneTransitionLayoutForTesting(state = layoutState, modifier = Modifier.size(LayoutSize)) {
             scene(SceneA, userActions = mapOf(Back to SceneB)) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 751b314..11abbbe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -763,7 +763,7 @@
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
-            SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+            SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
                 scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
                     Box(Modifier.fillMaxSize())
                 }
@@ -837,7 +837,7 @@
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+                SceneTransitionLayoutForTesting(state, Modifier.size(layoutSize)) {
                     scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
                         Box(Modifier.fillMaxSize())
                     }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index bb511bc..8b56892 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -40,7 +40,7 @@
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
 import com.android.compose.animation.scene.Scale
 import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
 import com.android.compose.animation.scene.SceneTransitions
 import com.android.compose.animation.scene.TestScenes
 import com.android.compose.animation.scene.testNestedTransition
@@ -114,7 +114,7 @@
         @Composable
         (states: List<MutableSceneTransitionLayoutState>) -> Unit =
         { states ->
-            SceneTransitionLayout(states[0]) {
+            SceneTransitionLayoutForTesting(states[0]) {
                 scene(TestScenes.SceneA, content = { TestElement(elementVariant0A) })
                 scene(
                     TestScenes.SceneB,
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 6d47bab..e56d1be 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -30,5 +30,7 @@
     content: @Composable ContentScope.() -> Unit,
 ) {
     val state = rememberMutableSceneTransitionLayoutState(currentScene)
-    SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
+    SceneTransitionLayout(state, modifier, implicitTestTags = true) {
+        scene(currentScene, content = content)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index f94a7ed..a362a37 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -137,7 +137,7 @@
         },
         changeState = changeState,
         transitionLayout = { state ->
-            SceneTransitionLayout(state, layoutModifier) {
+            SceneTransitionLayout(state, layoutModifier, implicitTestTags = true) {
                 scene(fromScene, content = fromSceneContent)
                 scene(toScene, content = toSceneContent)
             }
@@ -163,7 +163,7 @@
             )
         },
         transitionLayout = { state ->
-            SceneTransitionLayout(state) {
+            SceneTransitionLayout(state, implicitTestTags = true) {
                 scene(fromScene) { fromSceneContent() }
                 overlay(overlay) { overlayContent() }
             }
@@ -191,7 +191,7 @@
             )
         },
         transitionLayout = { state ->
-            SceneTransitionLayout(state) {
+            SceneTransitionLayout(state, implicitTestTags = true) {
                 scene(toScene) { toSceneContent() }
                 overlay(overlay) { overlayContent() }
             }
@@ -223,7 +223,7 @@
             )
         },
         transitionLayout = { state ->
-            SceneTransitionLayout(state) {
+            SceneTransitionLayout(state, implicitTestTags = true) {
                 scene(currentScene) { currentSceneContent() }
                 overlay(from, alignment = fromAlignment) { fromContent() }
                 overlay(to, alignment = toAlignment) { toContent() }
@@ -273,7 +273,7 @@
                 }
             }
 
-            SceneTransitionLayout(state, layoutModifier) {
+            SceneTransitionLayout(state, layoutModifier, implicitTestTags = true) {
                 scene(fromScene, content = fromSceneContent)
                 scene(toScene, content = toSceneContent)
             }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 2e5b5b5..aad1276 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -113,8 +113,8 @@
 
     companion object {
         // 750ms @ 120hz -> 90 frames of animation
-        // In practice, 45 looks good enough
-        const val NUM_CLOCK_FONT_ANIMATION_STEPS = 45
+        // In practice, 30 looks good enough and limits our memory usage
+        const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
 
         val FLEX_TYPEFACE by lazy {
             // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index fe665e6..24b9e84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -84,6 +84,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
 import com.google.common.truth.Truth
 import junit.framework.Assert
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -280,9 +281,9 @@
                 kosmos.keyguardDismissTransitionInteractor,
                 { primaryBouncerInteractor },
                 executor,
-            ) {
-                deviceEntryInteractor
-            }
+                { deviceEntryInteractor },
+                { kosmos.windowRootViewBlurInteractor },
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 6f2082b..7051f81 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -24,53 +24,47 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dock.dockManager
-import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 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.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.phone.centralSurfaces
-import com.android.systemui.statusbar.phone.centralSurfacesOptional
 import com.android.systemui.testKosmos
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @EnableFlags(FLAG_COMMUNAL_HUB)
@@ -82,7 +76,8 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+            return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+                .andSceneContainer()
         }
     }
 
@@ -90,9 +85,22 @@
         mSetFlagsRule.setFlagsParameterization(flags)
     }
 
-    private val kosmos = testKosmos()
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
-    private lateinit var underTest: CommunalSceneStartable
+    private val Kosmos.underTest by
+        Kosmos.Fixture {
+            CommunalSceneStartable(
+                communalInteractor = communalInteractor,
+                communalSettingsInteractor = communalSettingsInteractor,
+                communalSceneInteractor = communalSceneInteractor,
+                keyguardInteractor = keyguardInteractor,
+                systemSettings = fakeSettings,
+                notificationShadeWindowController = notificationShadeWindowController,
+                bgScope = applicationCoroutineScope,
+                mainDispatcher = testDispatcher,
+                uiEventLogger = uiEventLoggerFake,
+            )
+        }
 
     @Before
     fun setUp() {
@@ -102,646 +110,314 @@
                 SCREEN_TIMEOUT,
                 UserHandle.USER_CURRENT,
             )
-            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
 
-            underTest =
-                CommunalSceneStartable(
-                        dockManager = dockManager,
-                        communalInteractor = communalInteractor,
-                        communalSettingsInteractor = communalSettingsInteractor,
-                        communalSceneInteractor = communalSceneInteractor,
-                        keyguardTransitionInteractor = keyguardTransitionInteractor,
-                        keyguardInteractor = keyguardInteractor,
-                        systemSettings = fakeSettings,
-                        notificationShadeWindowController = notificationShadeWindowController,
-                        applicationScope = applicationCoroutineScope,
-                        bgScope = applicationCoroutineScope,
-                        mainDispatcher = testDispatcher,
-                        centralSurfacesOpt = centralSurfacesOptional,
-                        uiEventLogger = uiEventLoggerFake,
-                    )
-                    .apply { start() }
+            underTest.start()
 
             // Make communal available so that communalInteractor.desiredScene accurately reflects
             // scene changes instead of just returning Blank.
-            with(kosmos.testScope) {
-                launch { setCommunalAvailable(true) }
-                testScheduler.runCurrent()
-            }
+            runBlocking { setCommunalAvailable(true) }
+            setCommunalV2ConfigEnabled(true)
         }
     }
 
     @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun keyguardGoesAway_whenLaunchingEditMode_doNotForceBlankScene() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                communalSceneInteractor.setEditModeState(EditModeState.STARTING)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
-                    testScope = this,
-                )
-
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                communalSceneInteractor.setIsLaunchingWidget(true)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
-                    testScope = this,
-                )
-
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                communalSceneInteractor.setIsLaunchingWidget(false)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
-                    testScope = this,
-                )
-
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun keyguardGoesAway_whenInEditMode_doesNotChangeScene() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                communalInteractor.setEditModeOpen(true)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
-                    to = KeyguardState.GONE,
-                    testScope = this,
-                )
-                // Scene change will be handled in EditWidgetsActivity not here
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Ignore("Ignored until custom animations are implemented in b/322787129")
-    @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
-    fun deviceDocked_forceCommunalScene() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
-                updateDocked(true)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.LOCKSCREEN,
-                    testScope = this,
-                )
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun occluded_forceBlankScene() =
-        with(kosmos) {
-            testScope.runTest {
-                whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false)
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                updateDocked(true)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.OCCLUDED,
-                    testScope = this,
-                )
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun occluded_doesNotForceBlankSceneIfLaunchingActivityOverLockscreen() =
-        with(kosmos) {
-            testScope.runTest {
-                whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true)
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                updateDocked(true)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.OCCLUDED,
-                    testScope = this,
-                )
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
-                updateDocked(true)
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    testScope = this,
-                )
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun deviceAsleep_forceBlankSceneAfterTimeout() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.OFF,
-                    testScope = this,
-                )
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
-
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun deviceAsleep_wakesUpBeforeTimeout_noChangeInScene() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.OFF,
-                    testScope = this,
-                )
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-                advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.OFF,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    testScope = this,
-                )
-
-                advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Ignore("Ignored until custom animations are implemented in b/322787129")
-    @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
-    fun dockingOnLockscreen_forcesCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-
-                // device is docked while on the lockscreen
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    testScope = this,
-                )
-                updateDocked(true)
-
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-                advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    @Ignore("Ignored until custom animations are implemented in b/322787129")
-    @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
-    fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
-        with(kosmos) {
-            testScope.runTest {
-                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-
-                // device is docked while on the lockscreen
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    testScope = this,
-                )
-                updateDocked(true)
-
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-                advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY / 2)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
-                // dream starts shortly after docking
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.DREAMING,
-                    testScope = this,
-                )
-                advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
-        }
-
-    @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_whenDreaming_goesToBlank() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        kosmos.runTest {
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Scene times out back to blank after the screen timeout.
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
+            // Scene times out back to blank after the screen timeout.
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Blank)
         }
 
     @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_notDreaming_staysOnCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is not dreaming and on communal.
-                updateDreaming(false)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        kosmos.runTest {
+            // Device is not dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(false)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                // Scene stays as Communal
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
+            // Scene stays as Communal
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
         }
 
     @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_dreamStopped_staysOnCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        kosmos.runTest {
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Wait a bit, but not long enough to timeout.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            // Wait a bit, but not long enough to timeout.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Dream stops, timeout is cancelled and device stays on hub, because the regular
-                // screen timeout will take effect at this point.
-                updateDreaming(false)
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
+            // Dream stops, timeout is cancelled and device stays on hub, because the regular
+            // screen timeout will take effect at this point.
+            fakeKeyguardRepository.setDreaming(false)
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
         }
 
     @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_dreamStartedHalfway_goesToCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is on communal, but not dreaming.
-                updateDreaming(false)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        kosmos.runTest {
+            // Device is on communal, but not dreaming.
+            fakeKeyguardRepository.setDreaming(false)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Wait a bit, but not long enough to timeout, then start dreaming.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                updateDreaming(true)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            // Wait a bit, but not long enough to timeout, then start dreaming.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            fakeKeyguardRepository.setDreaming(true)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Device times out after one screen timeout interval, dream doesn't reset timeout.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
+            // Device times out after one screen timeout interval, dream doesn't reset timeout.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Blank)
         }
 
     @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is on communal.
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        kosmos.runTest {
+            // Device is on communal.
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                // Device stays on the hub after the timeout since we're not dreaming.
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            // Device stays on the hub after the timeout since we're not dreaming.
+            testScope.advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Start dreaming.
-                updateDreaming(true)
-                advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+            // Start dreaming.
+            fakeKeyguardRepository.setDreaming(true)
+            advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS.milliseconds)
 
-                // Hub times out immediately.
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
+            // Hub times out immediately.
+            assertThat(scene).isEqualTo(CommunalScenes.Blank)
         }
 
     @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_userActivityTriggered_resetsTimeout() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        kosmos.runTest {
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Wait a bit, but not long enough to timeout.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            // Wait a bit, but not long enough to timeout.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
 
-                // Send user interaction to reset timeout.
-                communalInteractor.signalUserInteraction()
+            // Send user interaction to reset timeout.
+            communalInteractor.signalUserInteraction()
 
-                // If user activity didn't reset timeout, we would have gone back to Blank by now.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            // If user activity didn't reset timeout, we would have gone back to Blank by now.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Timeout happens one interval after the user interaction.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-            }
+            // Timeout happens one interval after the user interaction.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Blank)
         }
 
     @Test
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_screenTimeoutChanged() =
-        with(kosmos) {
-            testScope.runTest {
-                fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+        kosmos.runTest {
+            fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
 
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                // Scene times out back to blank after the screen timeout.
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            // Scene times out back to blank after the screen timeout.
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-                assertThat(uiEventLoggerFake.logs.first().eventId)
-                    .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
-                assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-            }
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            assertThat(uiEventLoggerFake.logs.first().eventId)
+                .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_whenDreaming_goesToBlank() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+        kosmos.runTest {
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Scene times out back to blank after the screen timeout.
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Dream)
-            }
+            // Scene times out back to blank after the screen timeout.
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Dream)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_notDreaming_staysOnCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is not dreaming and on communal.
-                updateDreaming(false)
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+        kosmos.runTest {
+            // Device is not dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(false)
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                // Scene stays as Communal
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
-            }
+            // Scene stays as Communal
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_dreamStopped_staysOnCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+        kosmos.runTest {
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Wait a bit, but not long enough to timeout.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            // Wait a bit, but not long enough to timeout.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Dream stops, timeout is cancelled and device stays on hub, because the regular
-                // screen timeout will take effect at this point.
-                updateDreaming(false)
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Communal)
-            }
+            // Dream stops, timeout is cancelled and device stays on hub, because the regular
+            // screen timeout will take effect at this point.
+            fakeKeyguardRepository.setDreaming(false)
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Communal)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_dreamStartedHalfway_goesToCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is on communal, but not dreaming.
-                updateDreaming(false)
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+        kosmos.runTest {
+            // Device is on communal, but not dreaming.
+            fakeKeyguardRepository.setDreaming(false)
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Wait a bit, but not long enough to timeout, then start dreaming.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                updateDreaming(true)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            // Wait a bit, but not long enough to timeout, then start dreaming.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            fakeKeyguardRepository.setDreaming(true)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Device times out after one screen timeout interval, dream doesn't reset timeout.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Dream)
-            }
+            // Device times out after one screen timeout interval, dream doesn't reset timeout.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Dream)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_dreamAfterInitialTimeout_goesToBlank() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is on communal.
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+        kosmos.runTest {
+            // Device is on communal.
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                // Device stays on the hub after the timeout since we're not dreaming.
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            // Device stays on the hub after the timeout since we're not dreaming.
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Start dreaming.
-                updateDreaming(true)
-                advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+            // Start dreaming.
+            fakeKeyguardRepository.setDreaming(true)
+            advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS.milliseconds)
 
-                // Hub times out immediately.
-                assertThat(scene).isEqualTo(Scenes.Dream)
-            }
+            // Hub times out immediately.
+            assertThat(scene).isEqualTo(Scenes.Dream)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_userActivityTriggered_resetsTimeout() =
-        with(kosmos) {
-            testScope.runTest {
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+        kosmos.runTest {
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Wait a bit, but not long enough to timeout.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            // Wait a bit, but not long enough to timeout.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
 
-                // Send user interaction to reset timeout.
-                communalInteractor.signalUserInteraction()
+            // Send user interaction to reset timeout.
+            communalInteractor.signalUserInteraction()
 
-                // If user activity didn't reset timeout, we would have gone back to Blank by now.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            // If user activity didn't reset timeout, we would have gone back to Blank by now.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Timeout happens one interval after the user interaction.
-                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Dream)
-            }
+            // Timeout happens one interval after the user interaction.
+            advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Dream)
         }
 
     @Test
     @EnableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun hubTimeout_withSceneContainer_screenTimeoutChanged() =
-        with(kosmos) {
-            testScope.runTest {
-                fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+        kosmos.runTest {
+            fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
 
-                // Device is dreaming and on communal.
-                updateDreaming(true)
-                sceneInteractor.changeScene(Scenes.Communal, "test")
+            // Device is dreaming and on communal.
+            fakeKeyguardRepository.setDreaming(true)
+            sceneInteractor.changeScene(Scenes.Communal, "test")
 
-                val scene by collectLastValue(sceneInteractor.currentScene)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            val scene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                // Scene times out back to blank after the screen timeout.
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Communal)
+            // Scene times out back to blank after the screen timeout.
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Communal)
 
-                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
-                assertThat(scene).isEqualTo(Scenes.Dream)
-                assertThat(uiEventLoggerFake.logs.first().eventId)
-                    .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
-                assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-            }
+            advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+            assertThat(scene).isEqualTo(Scenes.Dream)
+            assertThat(uiEventLoggerFake.logs.first().eventId)
+                .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
         }
 
-    @Test
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_SCENE_CONTAINER)
-    fun transitionFromDozingToGlanceableHub_forcesCommunal() =
-        with(kosmos) {
-            testScope.runTest {
-                val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
-                assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    testScope = this,
-                )
-
-                assertThat(scene).isEqualTo(CommunalScenes.Communal)
-            }
-        }
-
-    private fun TestScope.updateDocked(docked: Boolean) =
-        with(kosmos) {
-            runCurrent()
-            fakeDockManager.setIsDocked(docked)
-            // TODO(b/322787129): uncomment once custom animations are in place
-            // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
-            runCurrent()
-        }
-
-    private fun TestScope.updateDreaming(dreaming: Boolean) =
-        with(kosmos) {
-            fakeKeyguardRepository.setDreaming(dreaming)
-            runCurrent()
-        }
+    /**
+     * Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at
+     * currentTime + duration are scheduled.
+     */
+    private fun Kosmos.advanceTimeBy(duration: Duration) =
+        testScope.advanceTimeBy(duration + 1.milliseconds)
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index ff722bf..aa96073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -22,7 +22,6 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.ObservableTransitionState.Idle
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.communalSceneRepository
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -64,7 +63,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@EnableFlags(FLAG_COMMUNAL_HUB, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+@EnableFlags(FLAG_COMMUNAL_HUB)
 @DisableSceneContainer
 class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 1a3606e..da25bca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -44,6 +45,7 @@
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -65,7 +67,7 @@
 
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
-    private val sceneInteractor = kosmos.sceneInteractor
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
     private val underTest: CommunalTransitionViewModel by lazy {
         kosmos.communalTransitionViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 18cc8bf..f5f5dd8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -23,24 +23,19 @@
 import android.content.pm.UserInfo
 import android.provider.Settings
 import android.view.accessibility.AccessibilityEvent
-import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.accessibilityManager
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -49,12 +44,15 @@
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.controller.mediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -62,73 +60,45 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
-    @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var uiEventLogger: UiEventLogger
-    @Mock private lateinit var packageManager: PackageManager
-    @Mock private lateinit var metricsLogger: CommunalMetricsLogger
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private lateinit var tutorialRepository: FakeCommunalTutorialRepository
-    private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
-    private lateinit var mediaRepository: FakeCommunalMediaRepository
-    private lateinit var communalSceneInteractor: CommunalSceneInteractor
-    private lateinit var communalInteractor: CommunalInteractor
-    private lateinit var accessibilityManager: AccessibilityManager
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
     private val testableResources = context.orCreateTestableResources
 
-    private lateinit var underTest: CommunalEditModeViewModel
+    private val Kosmos.packageManager by Kosmos.Fixture { mock<PackageManager>() }
 
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
+    private val Kosmos.metricsLogger by Kosmos.Fixture { mock<CommunalMetricsLogger>() }
 
-        tutorialRepository = kosmos.fakeCommunalTutorialRepository
-        widgetRepository = kosmos.fakeCommunalWidgetRepository
-        smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
-        mediaRepository = kosmos.fakeCommunalMediaRepository
-        communalSceneInteractor = kosmos.communalSceneInteractor
-        communalInteractor = spy(kosmos.communalInteractor)
-        kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
-        kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
-        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
-        accessibilityManager = kosmos.accessibilityManager
-
-        underTest =
+    private val Kosmos.underTest by
+        Kosmos.Fixture {
             CommunalEditModeViewModel(
                 communalSceneInteractor,
                 communalInteractor,
-                kosmos.communalSettingsInteractor,
-                kosmos.keyguardTransitionInteractor,
-                mediaHost,
+                communalSettingsInteractor,
+                keyguardTransitionInteractor,
+                mock<MediaHost>(),
                 uiEventLogger,
                 logcatLogBuffer("CommunalEditModeViewModelTest"),
-                kosmos.testDispatcher,
+                testDispatcher,
                 metricsLogger,
                 context,
                 accessibilityManager,
@@ -136,19 +106,28 @@
                 WIDGET_PICKER_PACKAGE_NAME,
                 kosmos.mediaCarouselController,
             )
+        }
+
+    @Before
+    fun setUp() {
+        kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+        kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
+        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
     }
 
     @Test
     fun communalContent_onlyWidgetsAndCtaTileAreShownInEditMode() =
-        testScope.runTest {
-            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        kosmos.runTest {
+            fakeCommunalTutorialRepository.setTutorialSettingState(
+                Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+            )
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
-            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Smartspace available.
-            smartspaceRepository.setTimers(
+            fakeCommunalSmartspaceRepository.setTimers(
                 listOf(
                     CommunalSmartspaceTimer(
                         smartspaceTargetId = "target",
@@ -159,7 +138,7 @@
             )
 
             // Media playing.
-            mediaRepository.mediaActive()
+            fakeCommunalMediaRepository.mediaActive()
 
             val communalContent by collectLastValue(underTest.communalContent)
 
@@ -173,7 +152,7 @@
 
     @Test
     fun selectedKey_onReorderWidgets_isSet() =
-        testScope.runTest {
+        kosmos.runTest {
             val selectedKey by collectLastValue(underTest.selectedKey)
 
             underTest.setSelectedKey(null)
@@ -186,7 +165,7 @@
 
     @Test
     fun isCommunalContentVisible_isTrue_whenEditModeShowing() =
-        testScope.runTest {
+        kosmos.runTest {
             val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
             communalSceneInteractor.setEditModeState(EditModeState.SHOWING)
             assertThat(isCommunalContentVisible).isEqualTo(true)
@@ -194,7 +173,7 @@
 
     @Test
     fun isCommunalContentVisible_isFalse_whenEditModeNotShowing() =
-        testScope.runTest {
+        kosmos.runTest {
             val isCommunalContentVisible by collectLastValue(underTest.isCommunalContentVisible)
             communalSceneInteractor.setEditModeState(null)
             assertThat(isCommunalContentVisible).isEqualTo(false)
@@ -202,12 +181,14 @@
 
     @Test
     fun deleteWidget() =
-        testScope.runTest {
-            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        kosmos.runTest {
+            fakeCommunalTutorialRepository.setTutorialSettingState(
+                Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+            )
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
-            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             val communalContent by collectLastValue(underTest.communalContent)
 
@@ -220,6 +201,7 @@
 
             underTest.onDeleteWidget(
                 id = 0,
+                key = "key_0",
                 componentName = ComponentName("test_package", "test_class"),
                 rank = 30,
             )
@@ -233,26 +215,56 @@
         }
 
     @Test
-    fun reorderWidget_uiEventLogging_start() {
-        underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
-        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
-    }
+    fun deleteWidget_clearsSelectedKey() =
+        kosmos.runTest {
+            val selectedKey by collectLastValue(underTest.selectedKey)
+            underTest.setSelectedKey("test_key")
+            assertThat(selectedKey).isEqualTo("test_key")
+
+            // Selected key is deleted.
+            underTest.onDeleteWidget(
+                id = 0,
+                key = "test_key",
+                componentName = ComponentName("test_package", "test_class"),
+                rank = 30,
+            )
+
+            assertThat(selectedKey).isNull()
+        }
 
     @Test
-    fun reorderWidget_uiEventLogging_end() {
-        underTest.onReorderWidgetEnd()
-        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
-    }
+    fun reorderWidget_uiEventLogging_start() =
+        kosmos.runTest {
+            underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+            assertThat(uiEventLoggerFake.logs[0].eventId)
+                .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START.id)
+        }
 
     @Test
-    fun reorderWidget_uiEventLogging_cancel() {
-        underTest.onReorderWidgetCancel()
-        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
-    }
+    fun reorderWidget_uiEventLogging_end() =
+        kosmos.runTest {
+            underTest.onReorderWidgetEnd()
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+            assertThat(uiEventLoggerFake.logs[0].eventId)
+                .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH.id)
+        }
+
+    @Test
+    fun reorderWidget_uiEventLogging_cancel() =
+        kosmos.runTest {
+            underTest.onReorderWidgetCancel()
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+            assertThat(uiEventLoggerFake.logs[0].eventId)
+                .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL.id)
+        }
 
     @Test
     fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
-        testScope.runTest {
+        kosmos.runTest {
             var activityStarted = false
             val success =
                 underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
@@ -266,7 +278,7 @@
 
     @Test
     fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
-        testScope.runTest {
+        kosmos.runTest {
             val success =
                 underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
                     run { throw ActivityNotFoundException() }
@@ -278,7 +290,7 @@
 
     @Test
     fun showDisclaimer_trueAfterEditModeShowing() =
-        testScope.runTest {
+        kosmos.runTest {
             val showDisclaimer by collectLastValue(underTest.showDisclaimer)
 
             assertThat(showDisclaimer).isFalse()
@@ -288,9 +300,9 @@
 
     @Test
     fun showDisclaimer_falseWhenDismissed() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.setEditModeState(EditModeState.SHOWING)
-            kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
 
             val showDisclaimer by collectLastValue(underTest.showDisclaimer)
 
@@ -301,63 +313,67 @@
 
     @Test
     fun showDisclaimer_trueWhenTimeout() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.setEditModeState(EditModeState.SHOWING)
-            kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
 
             val showDisclaimer by collectLastValue(underTest.showDisclaimer)
 
             assertThat(showDisclaimer).isTrue()
             underTest.onDisclaimerDismissed()
             assertThat(showDisclaimer).isFalse()
-            advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+            testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS + 1.milliseconds)
             assertThat(showDisclaimer).isTrue()
         }
 
     @Test
-    fun scrollPosition_persistedOnEditCleanup() {
-        val index = 2
-        val offset = 30
-        underTest.onScrollPositionUpdated(index, offset)
-        underTest.cleanupEditModeState()
+    fun scrollPosition_persistedOnEditCleanup() =
+        kosmos.runTest {
+            val index = 2
+            val offset = 30
+            underTest.onScrollPositionUpdated(index, offset)
+            underTest.cleanupEditModeState()
 
-        verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
-    }
+            assertThat(communalInteractor.firstVisibleItemIndex).isEqualTo(index)
+            assertThat(communalInteractor.firstVisibleItemOffset).isEqualTo(offset)
+        }
 
     @Test
-    fun onNewWidgetAdded_accessibilityDisabled_doNothing() {
-        whenever(accessibilityManager.isEnabled).thenReturn(false)
+    fun onNewWidgetAdded_accessibilityDisabled_doNothing() =
+        kosmos.runTest {
+            whenever(accessibilityManager.isEnabled).thenReturn(false)
 
-        val provider =
-            mock<AppWidgetProviderInfo> {
-                on { loadLabel(packageManager) }.thenReturn("Test Clock")
-            }
-        underTest.onNewWidgetAdded(provider)
+            val provider =
+                mock<AppWidgetProviderInfo> {
+                    on { loadLabel(packageManager) }.thenReturn("Test Clock")
+                }
+            underTest.onNewWidgetAdded(provider)
 
-        verify(accessibilityManager, never()).sendAccessibilityEvent(any())
-    }
+            verify(accessibilityManager, never()).sendAccessibilityEvent(any())
+        }
 
     @Test
-    fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() {
-        whenever(accessibilityManager.isEnabled).thenReturn(true)
+    fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() =
+        kosmos.runTest {
+            whenever(accessibilityManager.isEnabled).thenReturn(true)
 
-        val provider =
-            mock<AppWidgetProviderInfo> {
-                on { loadLabel(packageManager) }.thenReturn("Test Clock")
-            }
-        underTest.onNewWidgetAdded(provider)
+            val provider =
+                mock<AppWidgetProviderInfo> {
+                    on { loadLabel(packageManager) }.thenReturn("Test Clock")
+                }
+            underTest.onNewWidgetAdded(provider)
 
-        val captor = argumentCaptor<AccessibilityEvent>()
-        verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
+            val captor = argumentCaptor<AccessibilityEvent>()
+            verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
 
-        val event = captor.firstValue
-        assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
-        assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
-    }
+            val event = captor.firstValue
+            assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+            assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
+        }
 
     @Test
     fun onResizeWidget_logsMetrics() =
-        testScope.runTest {
+        kosmos.runTest {
             val appWidgetId = 123
             val spanY = 2
             val widgetIdToRankMap = mapOf(appWidgetId to 1)
@@ -372,7 +388,6 @@
                 rank = rank,
             )
 
-            verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
             verify(metricsLogger)
                 .logResizeWidget(
                     componentName = componentName.flattenToString(),
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 8515515..799054a 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
@@ -78,6 +78,7 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -120,6 +121,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
+    @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
     @Mock private lateinit var metricsLogger: CommunalMetricsLogger
 
     private val kosmos = testKosmos()
@@ -161,6 +163,8 @@
 
         kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
         whenever(mediaHost.visible).thenReturn(true)
+        whenever(kosmos.mediaCarouselController.mediaCarouselScrollHandler)
+            .thenReturn(mediaCarouselScrollHandler)
 
         kosmos.powerInteractor.setAwakeForTest()
 
@@ -187,6 +191,7 @@
             metricsLogger,
             kosmos.mediaCarouselController,
             kosmos.blurConfig,
+            false,
         )
     }
 
@@ -903,6 +908,20 @@
         }
 
     @Test
+    fun onShowPreviousMedia_scrollHandler_isCalled() =
+        testScope.runTest {
+            underTest.onShowPreviousMedia()
+            verify(mediaCarouselScrollHandler).scrollByStep(-1)
+        }
+
+    @Test
+    fun onShowNextMedia_scrollHandler_isCalled() =
+        testScope.runTest {
+            underTest.onShowNextMedia()
+            verify(mediaCarouselScrollHandler).scrollByStep(1)
+        }
+
+    @Test
     @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
     fun uiIsBlurred_whenPrimaryBouncerIsShowing() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 329627a..e36d245 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -61,6 +61,7 @@
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.eq
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
@@ -73,6 +74,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
@@ -80,21 +82,26 @@
     private val testScope: TestScope = kosmos.testScope
 
     private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
+
     private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
     private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
     private val fakeUserRepository = kosmos.fakeUserRepository
     private val facePropertyRepository = kosmos.facePropertyRepository
-    private val fakeDeviceEntryFingerprintAuthInteractor =
-        kosmos.deviceEntryFingerprintAuthInteractor
-    private val powerInteractor = kosmos.powerInteractor
     private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
 
-    private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+    private val keyguardUpdateMonitor by lazy { kosmos.keyguardUpdateMonitor }
     private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
     private val trustManager = kosmos.trustManager
-    private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
+
+    private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+    private val fakeDeviceEntryFingerprintAuthInteractor by lazy {
+        kosmos.deviceEntryFingerprintAuthInteractor
+    }
+    private val powerInteractor by lazy { kosmos.powerInteractor }
+    private val deviceEntryFaceAuthStatusInteractor by lazy {
+        kosmos.deviceEntryFaceAuthStatusInteractor
+    }
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
index e0082da..ff5fa39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.Notification
 import android.app.NotificationManager
+import android.service.notification.StatusBarNotification
 import androidx.annotation.StringRes
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -28,6 +29,7 @@
 import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.kosmos.backgroundScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.settings.userTracker
 import com.android.systemui.statusbar.commandline.commandRegistry
@@ -35,6 +37,7 @@
 import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,23 +47,29 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.times
 import org.mockito.junit.MockitoJUnit
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
+import org.mockito.kotlin.firstValue
 import org.mockito.kotlin.never
+import org.mockito.kotlin.secondValue
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TutorialNotificationCoordinatorTest : SysuiTestCase() {
 
     private lateinit var underTest: TutorialNotificationCoordinator
-    private val kosmos = testKosmos()
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val keyboardRepository = FakeKeyboardRepository()
     private val touchpadRepository = FakeTouchpadRepository()
     private lateinit var repository: TutorialSchedulerRepository
     @Mock private lateinit var notificationManager: NotificationManager
+    @Mock private lateinit var notification: StatusBarNotification
     @Captor private lateinit var notificationCaptor: ArgumentCaptor<Notification>
     @get:Rule val rule = MockitoJUnit.rule()
 
@@ -107,6 +116,7 @@
     fun showTouchpadNotification() = runTestAndClear {
         touchpadRepository.setIsAnyTouchpadConnected(true)
         testScope.advanceTimeBy(LAUNCH_DELAY)
+        mockExistingNotification()
         verifyNotification(
             R.string.launch_touchpad_tutorial_notification_title,
             R.string.launch_touchpad_tutorial_notification_content,
@@ -131,6 +141,45 @@
             .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), any(), any())
     }
 
+    @Test
+    fun showKeyboardNotificationThenDisconnectKeyboard() = runTestAndClear {
+        keyboardRepository.setIsAnyKeyboardConnected(true)
+        testScope.advanceTimeBy(LAUNCH_DELAY)
+        verifyNotification(
+            R.string.launch_keyboard_tutorial_notification_title,
+            R.string.launch_keyboard_tutorial_notification_content,
+        )
+        mockExistingNotification()
+
+        // After the keyboard is disconnected, i.e. there is nothing connected, the notification
+        // should be cancelled
+        keyboardRepository.setIsAnyKeyboardConnected(false)
+        verify(notificationManager).cancelAsUser(eq(TAG), eq(NOTIFICATION_ID), any())
+    }
+
+    @Test
+    fun showKeyboardTouchpadNotificationThenDisconnectKeyboard() = runTestAndClear {
+        keyboardRepository.setIsAnyKeyboardConnected(true)
+        touchpadRepository.setIsAnyTouchpadConnected(true)
+        testScope.advanceTimeBy(LAUNCH_DELAY)
+        mockExistingNotification()
+        keyboardRepository.setIsAnyKeyboardConnected(false)
+
+        verify(notificationManager, times(2))
+            .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+        // Connect both device and the first notification is for both
+        notificationCaptor.firstValue.verify(
+            R.string.launch_keyboard_touchpad_tutorial_notification_title,
+            R.string.launch_keyboard_touchpad_tutorial_notification_content,
+        )
+        // After the keyboard is disconnected, i.e. with only the touchpad left, the notification
+        // should be update to the one for only touchpad
+        notificationCaptor.secondValue.verify(
+            R.string.launch_touchpad_tutorial_notification_title,
+            R.string.launch_touchpad_tutorial_notification_content,
+        )
+    }
+
     private fun runTestAndClear(block: suspend () -> Unit) =
         testScope.runTest {
             try {
@@ -140,12 +189,21 @@
             }
         }
 
+    // Assume that there's an existing notification when the updater checks activeNotifications
+    private fun mockExistingNotification() {
+        whenever(notification.id).thenReturn(NOTIFICATION_ID)
+        whenever(notificationManager.activeNotifications).thenReturn(arrayOf(notification))
+    }
+
     private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
         verify(notificationManager)
             .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
-        val notification = notificationCaptor.value
-        val actualTitle = notification.getString(Notification.EXTRA_TITLE)
-        val actualContent = notification.getString(Notification.EXTRA_TEXT)
+        notificationCaptor.value.verify(titleResId, contentResId)
+    }
+
+    private fun Notification.verify(@StringRes titleResId: Int, @StringRes contentResId: Int) {
+        val actualTitle = getString(Notification.EXTRA_TITLE)
+        val actualContent = getString(Notification.EXTRA_TEXT)
         assertThat(actualTitle).isEqualTo(context.getString(titleResId))
         assertThat(actualContent).isEqualTo(context.getString(contentResId))
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index e13f3f1..9be786f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -20,18 +20,23 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
 import android.service.dream.dreamManager
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+import com.android.systemui.Flags.glanceableHubV2
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
 import com.android.systemui.communal.data.repository.communalSceneRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
@@ -56,6 +61,8 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.flow.flowOf
@@ -93,7 +100,7 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+            return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
         }
     }
 
@@ -107,6 +114,7 @@
 
         // Transition to DOZING and set the power interactor asleep.
         kosmos.powerInteractor.setAsleepForTest()
+        kosmos.setCommunalV2ConfigEnabled(true)
         runBlocking {
             kosmos.transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -160,26 +168,20 @@
 
     @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
-        kosmos.runTest {
-            whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
-            setCommunalAvailable(true)
-
-            powerInteractor.setAwakeForTest()
-
-            // If dreaming is possible and communal is available, then we should transition to
-            // GLANCEABLE_HUB when waking up due to power button press.
-            assertThat(transitionRepository)
-                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
-        }
-
-    @Test
-    @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
         kosmos.runTest {
             setCommunalAvailable(true)
-            whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            if (glanceableHubV2()) {
+                val user = fakeUserRepository.asMainUser()
+                fakeSettings.putIntForUser(
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                    1,
+                    user.id,
+                )
+                batteryRepository.fake.setDevicePluggedIn(true)
+            } else {
+                whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            }
 
             clearInvocations(fakeCommunalSceneRepository)
             powerInteractor.setAwakeForTest()
@@ -219,28 +221,21 @@
         }
 
     @Test
-    @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
-        kosmos.runTest {
-            fakeCommunalSceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
-            )
-
-            powerInteractor.setAwakeForTest()
-
-            // Under default conditions, we should transition to LOCKSCREEN when waking up.
-            assertThat(transitionRepository)
-                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
-        }
-
-    @Test
     @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER)
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
         kosmos.runTest {
             setCommunalAvailable(true)
-            whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            if (glanceableHubV2()) {
+                val user = fakeUserRepository.asMainUser()
+                fakeSettings.putIntForUser(
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                    1,
+                    user.id,
+                )
+                batteryRepository.fake.setDevicePluggedIn(true)
+            } else {
+                whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            }
 
             // Device turns on.
             powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 8e10682..898a5c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -19,14 +19,19 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
 import android.service.dream.dreamManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
+import com.android.systemui.Flags.glanceableHubV2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
 import com.android.systemui.communal.data.repository.communalSceneRepository
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -46,6 +51,8 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.advanceTimeBy
@@ -66,7 +73,7 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+            return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
                 .andSceneContainer()
         }
     }
@@ -101,6 +108,7 @@
             )
             reset(kosmos.transitionRepository)
             kosmos.setCommunalAvailable(true)
+            kosmos.setCommunalV2ConfigEnabled(true)
         }
         kosmos.underTest.start()
     }
@@ -190,7 +198,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     fun testTransitionToGlanceableHubOnWake() =
         kosmos.runTest {
@@ -202,7 +209,17 @@
             reset(transitionRepository)
 
             setCommunalAvailable(true)
-            whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            if (glanceableHubV2()) {
+                val user = fakeUserRepository.asMainUser()
+                fakeSettings.putIntForUser(
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                    1,
+                    user.id,
+                )
+                batteryRepository.fake.setDevicePluggedIn(true)
+            } else {
+                whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            }
 
             // Device wakes up.
             powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 63ed040..9a8e095 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,16 +32,11 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -51,7 +46,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.testKosmos
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -101,21 +95,4 @@
             assertThat(transitionRepository)
                 .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.LOCKSCREEN)
         }
-
-    @Test
-    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
-        testScope.runTest {
-            kosmos.fakeCommunalSceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
-            )
-            runCurrent()
-
-            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(from = KeyguardState.OCCLUDED, to = KeyguardState.GLANCEABLE_HUB)
-        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 29e95cd..fee2dfc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -53,13 +54,15 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
     val kosmos = testKosmos()
-    val underTest = kosmos.keyguardTransitionInteractor
-    val repository = kosmos.fakeKeyguardTransitionRepository
     val testScope = kosmos.testScope
+    val repository = kosmos.fakeKeyguardTransitionRepository
+
+    val underTest by lazy { kosmos.keyguardTransitionInteractor }
 
     @Test
     fun transitionCollectorsReceivesOnlyAppropriateEvents() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d057f7a0..a090fab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
@@ -26,7 +25,6 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
 import com.android.systemui.Flags
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.Flags.glanceableHubV2
 import com.android.systemui.SysuiTestCase
@@ -135,8 +133,7 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-                .andSceneContainer()
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
         }
     }
 
@@ -468,38 +465,6 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun dozingToGlanceableHub() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to DOZING
-            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.DOZING)
-            runCurrent()
-
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            // WHEN the device begins to wake
-            keyguardRepository.setKeyguardShowing(true)
-            powerInteractor.setAwakeForTest()
-            advanceTimeBy(60L)
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
     fun goneToDozing() =
         testScope.runTest {
             // GIVEN a device with AOD not available
@@ -625,40 +590,6 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun goneToGlanceableHub() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GONE
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
-
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            // WHEN the keyguard starts to show
-            keyguardRepository.setKeyguardShowing(true)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    from = KeyguardState.GONE,
-                    ownerName =
-                        FromGoneTransitionInteractor::class.simpleName +
-                            "(keyguard interactor says keyguard is showing)",
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @BrokenWithSceneContainer(339465026)
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun goneToGlanceableHub_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GONE
@@ -1070,84 +1001,6 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun occludedToGlanceableHub() =
-        testScope.runTest {
-            // GIVEN a device on lockscreen
-            keyguardRepository.setKeyguardShowing(true)
-            runCurrent()
-
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            // GIVEN a prior transition has run to OCCLUDED
-            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
-            keyguardRepository.setKeyguardOccluded(true)
-            runCurrent()
-
-            // WHEN occlusion ends
-            keyguardRepository.setKeyguardOccluded(false)
-            runCurrent()
-
-            // THEN a transition to GLANCEABLE_HUB should occur
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromOccludedTransitionInteractor::class.simpleName,
-                    from = KeyguardState.OCCLUDED,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @BrokenWithSceneContainer(339465026)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun occludedToGlanceableHubWhenInitiallyOnHub() =
-        testScope.runTest {
-            // GIVEN a device on lockscreen and communal is available
-            keyguardRepository.setKeyguardShowing(true)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
-            // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
-            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
-            keyguardRepository.setKeyguardOccluded(true)
-            runCurrent()
-
-            // GIVEN on blank scene
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Blank)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            // WHEN occlusion ends
-            keyguardRepository.setKeyguardOccluded(false)
-            runCurrent()
-
-            // THEN a transition to GLANCEABLE_HUB should occur
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromOccludedTransitionInteractor::class.simpleName,
-                    from = KeyguardState.OCCLUDED,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @BrokenWithSceneContainer(339465026)
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun occludedToGlanceableHub_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a device on lockscreen and communal is available
@@ -1390,48 +1243,6 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun dreamingToGlanceableHub() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to DREAMING
-            keyguardRepository.setDreaming(true)
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
-            runCurrent()
-
-            // WHEN a transition to the glanceable hub starts
-            val currentScene = CommunalScenes.Blank
-            val targetScene = CommunalScenes.Communal
-
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalInteractor.setTransitionState(transitionState)
-            progress.value = .1f
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromDreamingTransitionInteractor::class.simpleName,
-                    from = KeyguardState.DREAMING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun dreamingToGlanceableHub_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
@@ -1596,66 +1407,6 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun lockscreenToGlanceableHub() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to LOCKSCREEN
-            runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
-            runCurrent()
-
-            // WHEN a glanceable hub transition starts
-            val currentScene = CommunalScenes.Blank
-            val targetScene = CommunalScenes.Communal
-
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalInteractor.setTransitionState(transitionState)
-            progress.value = .1f
-            runCurrent()
-
-            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromLockscreenTransitionInteractor::class.simpleName,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            // WHEN the user stops dragging and the glanceable hub opening is cancelled
-            clearInvocations(transitionRepository)
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(currentScene)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromLockscreenTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun lockscreenToGlanceableHub_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1696,86 +1447,6 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToLockscreen() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
-            runCurrent()
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalInteractor.setTransitionState(transitionState)
-            progress.value = .1f
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            // WHEN the user stops dragging and the glanceable hub closing is cancelled
-            clearInvocations(transitionRepository)
-            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(currentScene)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToDozing() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
-
-            // WHEN the device begins to sleep
-            powerInteractor.setAsleepForTest()
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.DOZING,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDozing_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1844,39 +1515,6 @@
 
     @Test
     @BrokenWithSceneContainer(339465026)
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToOccluded() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
-            runCurrent()
-
-            // GIVEN the device is idle on the glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(CommunalScenes.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
-
-            // WHEN the keyguard is occluded
-            keyguardRepository.setKeyguardOccluded(true)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.OCCLUDED,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @BrokenWithSceneContainer(339465026)
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccluded_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN device is not dreaming
@@ -1905,30 +1543,6 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToGone() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
-
-            // WHEN keyguard goes away
-            keyguardRepository.setKeyguardGoingAway(true)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.GONE,
-                    animatorAssertion = { it.isNotNull() },
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToGone_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1953,7 +1567,7 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_GLANCEABLE_HUB_V2)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun glanceableHubToDreaming_v2() =
         testScope.runTest {
             kosmos.setCommunalV2Enabled(true)
@@ -1985,55 +1599,7 @@
         }
 
     @Test
-    @DisableSceneContainer
-    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToDreaming() =
-        testScope.runTest {
-            runCurrent()
-
-            // GIVEN that we are dreaming and not dozing
-            keyguardRepository.setDreaming(true)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
-            advanceTimeBy(600L)
-
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.GLANCEABLE_HUB)
-            runCurrent()
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = flowOf(0f, 0.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.DREAMING,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
     @BrokenWithSceneContainer(339465026)
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN that we are dreaming and not dozing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index a0fed6b..0ec3173 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -57,9 +57,7 @@
                     duration = 1000.milliseconds,
                     edge = Edge.create(from = Scenes.Gone, to = DREAMING),
                 )
-                .setupWithoutSceneContainer(
-                    edge = Edge.create(from = GONE, to = DREAMING),
-                )
+                .setupWithoutSceneContainer(edge = Edge.create(from = GONE, to = DREAMING))
     }
 
     @Test(expected = IllegalArgumentException::class)
@@ -75,7 +73,7 @@
                 underTest.sharedFlow(
                     startTime = 300.milliseconds,
                     duration = 800.milliseconds,
-                    onStep = { it }
+                    onStep = { it },
                 )
         }
 
@@ -112,6 +110,17 @@
         }
 
     @Test
+    fun onStepReturnsNullEmitsNothing() =
+        testScope.runTest {
+            val flow = underTest.sharedFlow(duration = 100.milliseconds, onStep = { null })
+            var animationValues = collectLastValue(flow)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+            assertThat(animationValues()).isNull()
+        }
+
+    @Test
     fun usesStartTime() =
         testScope.runTest {
             val flow =
@@ -166,11 +175,7 @@
     @Test
     fun usesOnStepToDoubleValue() =
         testScope.runTest {
-            val flow =
-                underTest.sharedFlow(
-                    duration = 1000.milliseconds,
-                    onStep = { it * 2 },
-                )
+            val flow = underTest.sharedFlow(duration = 1000.milliseconds, onStep = { it * 2 })
             val animationValues by collectLastValue(flow)
             runCurrent()
 
@@ -190,10 +195,7 @@
     fun usesOnStepToDoubleValueWithState() =
         testScope.runTest {
             val flow =
-                underTest.sharedFlowWithState(
-                    duration = 1000.milliseconds,
-                    onStep = { it * 2 },
-                )
+                underTest.sharedFlowWithState(duration = 1000.milliseconds, onStep = { it * 2 })
             val animationValues by collectLastValue(flow)
             runCurrent()
 
@@ -204,7 +206,7 @@
                         from = GONE,
                         to = DREAMING,
                         transitionState = TransitionState.STARTED,
-                        value = 0f
+                        value = 0f,
                     )
                 )
             repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
@@ -214,7 +216,7 @@
                         from = GONE,
                         to = DREAMING,
                         transitionState = TransitionState.RUNNING,
-                        value = 0.6f
+                        value = 0.6f,
                     )
                 )
             repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
@@ -224,7 +226,7 @@
                         from = GONE,
                         to = DREAMING,
                         transitionState = TransitionState.RUNNING,
-                        value = 1.2f
+                        value = 1.2f,
                     )
                 )
             repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
@@ -234,7 +236,7 @@
                         from = GONE,
                         to = DREAMING,
                         transitionState = TransitionState.RUNNING,
-                        value = 1.6f
+                        value = 1.6f,
                     )
                 )
             repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
@@ -244,7 +246,7 @@
                         from = GONE,
                         to = DREAMING,
                         transitionState = TransitionState.RUNNING,
-                        value = 2f
+                        value = 2f,
                     )
                 )
             repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
@@ -254,7 +256,7 @@
                         from = GONE,
                         to = DREAMING,
                         transitionState = TransitionState.FINISHED,
-                        value = null
+                        value = null,
                     )
                 )
         }
@@ -262,11 +264,7 @@
     @Test
     fun sameFloatValueWithTheSameTransitionStateDoesNotEmitTwice() =
         testScope.runTest {
-            val flow =
-                underTest.sharedFlow(
-                    duration = 1000.milliseconds,
-                    onStep = { it },
-                )
+            val flow = underTest.sharedFlow(duration = 1000.milliseconds, onStep = { it })
             val values by collectValues(flow)
             runCurrent()
 
@@ -280,11 +278,7 @@
     @Test
     fun sameFloatValueWithADifferentTransitionStateDoesEmitTwice() =
         testScope.runTest {
-            val flow =
-                underTest.sharedFlow(
-                    duration = 1000.milliseconds,
-                    onStep = { it },
-                )
+            val flow = underTest.sharedFlow(duration = 1000.milliseconds, onStep = { it })
             val values by collectValues(flow)
             runCurrent()
 
@@ -302,14 +296,14 @@
 
     private fun step(
         value: Float,
-        state: TransitionState = TransitionState.RUNNING
+        state: TransitionState = TransitionState.RUNNING,
     ): TransitionStep {
         return TransitionStep(
             from = GONE,
             to = DREAMING,
             value = value,
             transitionState = state,
-            ownerName = "GoneToDreamingTransitionViewModelTest"
+            ownerName = "GoneToDreamingTransitionViewModelTest",
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
index c8fade3..6648ae2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -63,7 +64,15 @@
             )
             runCurrent()
 
-            assertThat(alpha).isEqualTo(0.0f)
+            assertThat(alpha).isEqualTo(1.0f)
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                listOf(lockscreenToDozing(1f, FINISHED)),
+                testScope,
+            )
+            runCurrent()
+
+            assertThat(alpha).isEqualTo(0f)
         }
 
     private fun lockscreenToDozing(value: Float, state: TransitionState = RUNNING): TransitionStep {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index 47ca4b1..f357d0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -69,7 +69,7 @@
         @Parameters(
             name =
                 "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
-                    " isSingleShade={3}, isShadeTouchable={4}, isOccluded={6}"
+                    " isSingleShade={3}, isShadeTouchable={4}, isOccluded={5}"
         )
         @JvmStatic
         fun combinations() = buildList {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index d073cf1..c2f0ab9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.media.controls.ui.view
 
+import android.content.res.Resources
 import android.testing.TestableLooper
 import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -25,16 +28,21 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -42,7 +50,9 @@
 class MediaCarouselScrollHandlerTest : SysuiTestCase() {
 
     private val carouselWidth = 1038
+    private val settingsButtonWidth = 200
     private val motionEventUp = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)
+    private lateinit var testableLooper: TestableLooper
 
     @Mock lateinit var mediaCarousel: MediaScrollView
     @Mock lateinit var pageIndicator: PageIndicator
@@ -53,6 +63,9 @@
     @Mock lateinit var falsingManager: FalsingManager
     @Mock lateinit var logSmartspaceImpression: (Boolean) -> Unit
     @Mock lateinit var logger: MediaUiEventLogger
+    @Mock lateinit var contentContainer: ViewGroup
+    @Mock lateinit var settingsButton: View
+    @Mock lateinit var resources: Resources
 
     lateinit var executor: FakeExecutor
     private val clock = FakeSystemClock()
@@ -63,6 +76,11 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         executor = FakeExecutor(clock)
+        testableLooper = TestableLooper.get(this)
+        PhysicsAnimatorTestUtils.prepareForTest()
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+        whenever(mediaCarousel.contentContainer).thenReturn(contentContainer)
         mediaCarouselScrollHandler =
             MediaCarouselScrollHandler(
                 mediaCarousel,
@@ -74,13 +92,17 @@
                 closeGuts,
                 falsingManager,
                 logSmartspaceImpression,
-                logger
+                logger,
             )
         mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth
-
         whenever(mediaCarousel.touchListener).thenReturn(mediaCarouselScrollHandler.touchListener)
     }
 
+    @After
+    fun tearDown() {
+        PhysicsAnimatorTestUtils.tearDown()
+    }
+
     @Test
     fun testCarouselScroll_shortScroll() {
         whenever(mediaCarousel.isLayoutRtl).thenReturn(false)
@@ -128,4 +150,109 @@
 
         verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
     }
+
+    @Test
+    fun testCarouselScrollByStep_scrollRight() {
+        setupMediaContainer(visibleIndex = 0)
+
+        mediaCarouselScrollHandler.scrollByStep(1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel).smoothScrollTo(eq(carouselWidth), anyInt())
+    }
+
+    @Test
+    fun testCarouselScrollByStep_scrollLeft() {
+        setupMediaContainer(visibleIndex = 1)
+
+        mediaCarouselScrollHandler.scrollByStep(-1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
+    }
+
+    @Test
+    fun testCarouselScrollByStep_scrollRight_alreadyAtEnd() {
+        setupMediaContainer(visibleIndex = 1)
+
+        mediaCarouselScrollHandler.scrollByStep(1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+        verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+    }
+
+    @Test
+    fun testCarouselScrollByStep_scrollLeft_alreadyAtStart() {
+        setupMediaContainer(visibleIndex = 0)
+
+        mediaCarouselScrollHandler.scrollByStep(-1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+        verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+    }
+
+    @Test
+    fun testCarouselScrollByStep_scrollLeft_alreadyAtStart_isRTL() {
+        setupMediaContainer(visibleIndex = 0)
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+        whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+        mediaCarouselScrollHandler.scrollByStep(-1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+        verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+    }
+
+    @Test
+    fun testCarouselScrollByStep_scrollRight_alreadyAtEnd_isRTL() {
+        setupMediaContainer(visibleIndex = 1)
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+        whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+        mediaCarouselScrollHandler.scrollByStep(1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+        verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+    }
+
+    @Test
+    fun testScrollByStep_noScroll_notDismissible() {
+        setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+        mediaCarouselScrollHandler.scrollByStep(1)
+        clock.advanceTime(DISMISS_DELAY)
+        executor.runAllReady()
+
+        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+        verify(mediaCarousel, never()).animationTargetX = anyFloat()
+    }
+
+    private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
+        whenever(contentContainer.childCount).thenReturn(2)
+        val child1: View = mock()
+        val child2: View = mock()
+        whenever(child1.left).thenReturn(0)
+        whenever(child2.left).thenReturn(carouselWidth)
+        whenever(contentContainer.getChildAt(0)).thenReturn(child1)
+        whenever(contentContainer.getChildAt(1)).thenReturn(child2)
+
+        whenever(settingsButton.width).thenReturn(settingsButtonWidth)
+        whenever(settingsButton.context).thenReturn(context)
+        whenever(settingsButton.resources).thenReturn(resources)
+        whenever(settingsButton.resources.getDimensionPixelSize(anyInt())).thenReturn(20)
+        mediaCarouselScrollHandler.onSettingsButtonUpdated(settingsButton)
+
+        mediaCarouselScrollHandler.visibleMediaIndex = visibleIndex
+        mediaCarouselScrollHandler.showsSettingsButton = showsSettingsButton
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 847044a..95d9c8f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -386,6 +386,7 @@
         verify(mMediaSwitchingController).logInteractionAdjustVolume(mMediaDevice1);
     }
 
+    @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
     @Test
     public void onBindViewHolder_bindSelectableDevice_verifyView() {
         List<MediaDevice> selectableDevices = new ArrayList<>();
@@ -396,10 +397,55 @@
         assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.isChecked()).isFalse();
         assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
         assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+        mViewHolder.mCheckBox.performClick();
+        verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2);
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
+    @Test
+    public void onBindViewHolder_bindDeselectableDevice_verifyView() {
+        when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(
+                List.of(mMediaDevice1, mMediaDevice2));
+        when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(
+                List.of(mMediaDevice1, mMediaDevice2));
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.isChecked()).isTrue();
+        assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+
+        mViewHolder.mCheckBox.performClick();
+        verify(mMediaSwitchingController).removeDeviceFromPlayMedia(mMediaDevice2);
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
+    @Test
+    public void onBindViewHolder_changingSelectedValue_doesntTriggerChangeListener() {
+        List<MediaDevice> selectableDevices = List.of(mMediaDevice2);
+        List<MediaDevice> selectedDevices = new ArrayList<>();
+        selectedDevices.add(mMediaDevice1);
+        when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+        when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+
+        // mMediaDevice2 is selected
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+        assertThat(mViewHolder.mCheckBox.isChecked()).isFalse();
+
+        // changing the selected state programmatically (not a user click)
+        selectedDevices.add(mMediaDevice2);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+        assertThat(mViewHolder.mCheckBox.isChecked()).isTrue();
+
+        // The onCheckedChangeListener is not invoked
+        verify(mMediaSwitchingController, never()).addDeviceToPlayMedia(mMediaDevice2);
+        verify(mMediaSwitchingController, never()).removeDeviceFromPlayMedia(mMediaDevice2);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 896ca26..3c8857f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -17,11 +17,14 @@
 package com.android.systemui.qs
 
 import android.content.res.Configuration
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.ContextThemeWrapper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.haptics.qs.QSLongPressEffect
@@ -33,6 +36,7 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.leak.RotationUtils
 import javax.inject.Provider
@@ -63,6 +67,7 @@
     @Mock private lateinit var tile: QSTile
     @Mock private lateinit var tileLayout: TileLayout
     @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+    @Captor private lateinit var configCaptor: ArgumentCaptor<ConfigurationListener>
     @Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
     @Mock private lateinit var mediaCarouselInteractor: MediaCarouselInteractor
     @Mock private lateinit var configurationController: ConfigurationController
@@ -135,7 +140,8 @@
     }
 
     @Test
-    fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
+    @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed_old() {
         verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
 
         // verify that media starts in the expanded state by default
@@ -150,7 +156,24 @@
     }
 
     @Test
-    fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
+        verify(configurationController).addCallback(configCaptor.capture())
+
+        // verify that media starts in the expanded state by default
+        verify(mediaHost).expansion = MediaHostState.EXPANDED
+
+        // Rotate device, verify media size updated to collapsed
+        usingCollapsedLandscapeMedia = true
+        controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+        configCaptor.allValues.forEach { it.onConfigChanged(Configuration.EMPTY) }
+
+        verify(mediaHost).expansion = MediaHostState.COLLAPSED
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded_old() {
         verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
         reset(mediaHost)
 
@@ -161,6 +184,19 @@
         verify(mediaHost).expansion = MediaHostState.EXPANDED
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+        verify(configurationController).addCallback(configCaptor.capture())
+        reset(mediaHost)
+
+        usingCollapsedLandscapeMedia = false
+        controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+        configCaptor.allValues.forEach { it.onConfigChanged(Configuration.EMPTY) }
+
+        verify(mediaHost).expansion = MediaHostState.EXPANDED
+    }
+
     class TestQuickQSPanelController(
         view: QuickQSPanel,
         qsHost: QSHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index 264eda5..668c606 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import com.android.systemui.settings.userFileManager
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -76,11 +77,11 @@
     @Test
     fun setLargeTilesSpecs_inSharedPreferences() {
         val setA = setOf("tileA", "tileB")
-        underTest.setLargeTilesSpecs(setA.toTileSpecs())
+        underTest.writeLargeTileSpecs(setA.toTileSpecs())
         assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
 
         val setB = setOf("tileA", "tileB")
-        underTest.setLargeTilesSpecs(setB.toTileSpecs())
+        underTest.writeLargeTileSpecs(setB.toTileSpecs())
         assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
     }
 
@@ -92,12 +93,12 @@
 
                 fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
                 val setA = setOf("tileA", "tileB")
-                underTest.setLargeTilesSpecs(setA.toTileSpecs())
+                underTest.writeLargeTileSpecs(setA.toTileSpecs())
                 assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
 
                 fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
                 val setB = setOf("tileA", "tileB")
-                underTest.setLargeTilesSpecs(setB.toTileSpecs())
+                underTest.writeLargeTileSpecs(setB.toTileSpecs())
                 assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
 
                 fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -106,7 +107,7 @@
         }
 
     @Test
-    fun setInitialTilesFromSettings_noLargeTiles_tilesSet() =
+    fun setUpgradePathFromSettings_noLargeTiles_tilesSet() =
         with(kosmos) {
             testScope.runTest {
                 val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -117,14 +118,17 @@
 
                 assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
 
-                underTest.setInitialLargeTilesSpecs(tiles, PRIMARY_USER_ID)
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.ReadFromSettings(tiles),
+                    PRIMARY_USER_ID,
+                )
 
                 assertThat(largeTiles).isEqualTo(tiles)
             }
         }
 
     @Test
-    fun setInitialTilesFromSettings_alreadyLargeTiles_tilesNotSet() =
+    fun setUpgradePathFromSettings_alreadyLargeTiles_tilesNotSet() =
         with(kosmos) {
             testScope.runTest {
                 val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -133,14 +137,17 @@
                 fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
                 setLargeTilesSpecsInSharedPreferences(setOf("tileC"))
 
-                underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+                    ANOTHER_USER_ID,
+                )
 
                 assertThat(largeTiles).isEqualTo(setOf("tileC").toTileSpecs())
             }
         }
 
     @Test
-    fun setInitialTilesFromSettings_emptyLargeTiles_tilesNotSet() =
+    fun setUpgradePathFromSettings_emptyLargeTiles_tilesNotSet() =
         with(kosmos) {
             testScope.runTest {
                 val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -149,14 +156,17 @@
                 fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
                 setLargeTilesSpecsInSharedPreferences(emptySet())
 
-                underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+                    ANOTHER_USER_ID,
+                )
 
                 assertThat(largeTiles).isEmpty()
             }
         }
 
     @Test
-    fun setInitialTilesFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
+    fun setUpgradePathFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
         with(kosmos) {
             testScope.runTest {
                 val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -164,7 +174,10 @@
                 fakeUserRepository.setUserInfos(USERS)
                 fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
 
-                underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+                    ANOTHER_USER_ID,
+                )
 
                 assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
 
@@ -174,7 +187,7 @@
         }
 
     @Test
-    fun setInitialTiles_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
+    fun setUpgradePath_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
         with(kosmos) {
             testScope.runTest {
                 val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -186,14 +199,17 @@
                 assertThat(currentLargeTiles).isNotEmpty()
 
                 val tiles = setOf("tileA", "tileB")
-                underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+                    PRIMARY_USER_ID,
+                )
 
                 assertThat(largeTiles).isEqualTo(tiles.toTileSpecs())
             }
         }
 
     @Test
-    fun setInitialTiles_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
+    fun setUpgradePath_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
         with(kosmos) {
             testScope.runTest {
                 val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -204,15 +220,80 @@
 
                 assertThat(currentLargeTiles).isNotEmpty()
 
-                underTest.setLargeTilesSpecs(setOf(TileSpec.create("tileC")))
+                underTest.writeLargeTileSpecs(setOf(TileSpec.create("tileC")))
 
                 val tiles = setOf("tileA", "tileB")
-                underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+                    PRIMARY_USER_ID,
+                )
 
                 assertThat(largeTiles).isEqualTo(setOf(TileSpec.create("tileC")))
             }
         }
 
+    @Test
+    fun setTilesRestored_noLargeTiles_tilesSet() =
+        with(kosmos) {
+            testScope.runTest {
+                val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+                fakeUserRepository.setUserInfos(USERS)
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+                val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+                assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
+
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.RestoreFromBackup(tiles),
+                    PRIMARY_USER_ID,
+                )
+
+                assertThat(largeTiles).isEqualTo(tiles)
+            }
+        }
+
+    @Test
+    fun setDefaultTilesInitial_defaultSetLarge() =
+        with(kosmos) {
+            testScope.runTest {
+                val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+                fakeUserRepository.setUserInfos(USERS)
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.DefaultSet,
+                    PRIMARY_USER_ID,
+                )
+
+                assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+            }
+        }
+
+    @Test
+    fun setTilesRestored_afterDefaultSet_tilesSet() =
+        with(kosmos) {
+            testScope.runTest {
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.DefaultSet,
+                    PRIMARY_USER_ID,
+                )
+                val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+                fakeUserRepository.setUserInfos(USERS)
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+                val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+                underTest.setInitialOrUpgradeLargeTiles(
+                    TilesUpgradePath.RestoreFromBackup(tiles),
+                    PRIMARY_USER_ID,
+                )
+
+                assertThat(largeTiles).isEqualTo(tiles)
+            }
+        }
+
     private fun getSharedPreferences(): SharedPreferences =
         with(kosmos) {
             return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
new file mode 100644
index 0000000..f3c1f0c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.res.mainResources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.shared.model.PackageChangeModel.Empty.packageName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
+import com.android.systemui.qs.pipeline.data.repository.defaultTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
+import com.android.systemui.settings.userFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LargeTilesUpgradePathsTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply { defaultTilesRepository = DefaultTilesQSHostRepository(mainResources) }
+
+    private val defaultTiles = kosmos.defaultTilesRepository.defaultTiles.toSet()
+
+    private val underTest = kosmos.qsPreferencesInteractor
+
+    private val Kosmos.userId
+        get() = userRepository.getSelectedUserInfo().id
+
+    private val Kosmos.intent
+        get() =
+            Intent(ACTION_RESTORE_FINISHED).apply {
+                `package` = packageName
+                putExtra(Intent.EXTRA_USER_ID, kosmos.userId)
+                flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+            }
+
+    /**
+     * This test corresponds to the case of a fresh start.
+     *
+     * The resulting large tiles are the default set of large tiles.
+     */
+    @Test
+    fun defaultTiles_noDataInSharedPreferences_defaultLargeTiles() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+            assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+        }
+
+    /**
+     * This test corresponds to a user that upgraded in place from a build that didn't support large
+     * tiles to one that does. The current tiles of the user are read from settings.
+     *
+     * The resulting large tiles are those that were read from Settings.
+     */
+    @Test
+    fun upgradeInPlace_noDataInSharedPreferences_allLargeTiles() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            val tiles = setOf("a", "b", "c").toTileSpecs()
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.ReadFromSettings(tiles),
+                userId,
+            )
+
+            assertThat(largeTiles).isEqualTo(tiles)
+        }
+
+    /**
+     * This test corresponds to a fresh start, and then the user restarts the device, without ever
+     * having modified the set of large tiles.
+     *
+     * The resulting large tiles are the default large tiles that were set on the fresh start
+     */
+    @Test
+    fun defaultSet_restartDevice_largeTilesDontChange() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+            // User restarts the device, this will send a read from settings with the default
+            // set of tiles
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.ReadFromSettings(defaultTiles),
+                userId,
+            )
+
+            assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+        }
+
+    /**
+     * This test corresponds to a fresh start, following the user changing the sizes of some tiles.
+     * After that, the user restarts the device.
+     *
+     * The resulting set of large tiles are those that the user determined before restarting the
+     * device.
+     */
+    @Test
+    fun defaultSet_someSizeChanges_restart_correctSet() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+            underTest.setLargeTilesSpecs(largeTiles!! + setOf("a", "b").toTileSpecs())
+            val largeTilesBeforeRestart = largeTiles!!
+
+            // Restart
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.ReadFromSettings(defaultTiles),
+                userId,
+            )
+            assertThat(largeTiles).isEqualTo(largeTilesBeforeRestart)
+        }
+
+    /**
+     * This test corresponds to a user that upgraded, and after that performed some size changes.
+     * After that, the user restarts the device.
+     *
+     * The resulting set of large tiles are those that the user determined before restarting the
+     * device.
+     */
+    @Test
+    fun readFromSettings_changeSizes_restart_newLargeSet() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.ReadFromSettings(readTiles),
+                userId,
+            )
+            underTest.setLargeTilesSpecs(emptySet())
+
+            assertThat(largeTiles).isEmpty()
+
+            // Restart
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.ReadFromSettings(readTiles),
+                userId,
+            )
+            assertThat(largeTiles).isEmpty()
+        }
+
+    /**
+     * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+     * one that does, via restore from backup. Note that there's no file in SharedPreferences to
+     * restore.
+     *
+     * The resulting set of large tiles are those that were restored from the backup.
+     */
+    @Test
+    fun restoreFromBackup_noDataInSharedPreferences_allLargeTiles() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            val tiles = setOf("a", "b", "c").toTileSpecs()
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.RestoreFromBackup(tiles),
+                userId,
+            )
+
+            assertThat(largeTiles).isEqualTo(tiles)
+        }
+
+    /**
+     * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+     * one that does, via restore from backup. However, the restore happens after SystemUI's
+     * initialization has set the tiles to default. Note that there's no file in SharedPreferences
+     * to restore.
+     *
+     * The resulting set of large tiles are those that were restored from the backup.
+     */
+    @Test
+    fun restoreFromBackup_afterDefault_noDataInSharedPreferences_allLargeTiles() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+            val tiles = setOf("a", "b", "c").toTileSpecs()
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.RestoreFromBackup(tiles),
+                userId,
+            )
+
+            assertThat(largeTiles).isEqualTo(tiles)
+        }
+
+    /**
+     * This test corresponds to a user that restored from a build that supported different sizes
+     * tiles. First the list of tiles is restored in Settings and then a file containing some large
+     * tiles overrides the current shared preferences file
+     *
+     * The resulting set of large tiles are those that were restored from the shared preferences
+     * backup (and not the full list).
+     */
+    @Test
+    fun restoreFromBackup_thenRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            val tiles = setOf("a", "b", "c").toTileSpecs()
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.RestoreFromBackup(tiles),
+                userId,
+            )
+
+            val tilesFromBackupOfSharedPrefs = setOf("a")
+            setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+            broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+            assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+        }
+
+    /**
+     * This test corresponds to a user that restored from a build that supported different sizes
+     * tiles. However, this restore of settings happened after SystemUI's restore of the SharedPrefs
+     * containing the user's previous selections to large/small tiles.
+     *
+     * The resulting set of large tiles are those that were restored from the shared preferences
+     * backup (and not the full list).
+     */
+    @Test
+    fun restoreFromBackup_afterRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            val tiles = setOf("a", "b", "c").toTileSpecs()
+            val tilesFromBackupOfSharedPrefs = setOf("a")
+
+            setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+            broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.RestoreFromBackup(tiles),
+                userId,
+            )
+
+            assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+        }
+
+    /**
+     * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+     * one that does, via restore from backup. After that, the user modifies the size of some tiles
+     * and then restarts the device.
+     *
+     * The resulting set of large tiles are those after the user modifications.
+     */
+    @Test
+    fun restoreFromBackup_changeSizes_restart_newLargeSet() =
+        kosmos.runTest {
+            val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+            val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.RestoreFromBackup(readTiles),
+                userId,
+            )
+            underTest.setLargeTilesSpecs(emptySet())
+
+            assertThat(largeTiles).isEmpty()
+
+            // Restart
+            underTest.setInitialOrUpgradeLargeTilesSpecs(
+                TilesUpgradePath.ReadFromSettings(readTiles),
+                userId,
+            )
+            assertThat(largeTiles).isEmpty()
+        }
+
+    private companion object {
+        private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+
+        private fun Kosmos.getSharedPreferences(): SharedPreferences =
+            userFileManager.getSharedPreferences(
+                QSPreferencesRepository.FILE_NAME,
+                Context.MODE_PRIVATE,
+                userRepository.getSelectedUserInfo().id,
+            )
+
+        private fun Kosmos.setLargeTilesSpecsInSharedPreferences(specs: Set<String>) {
+            getSharedPreferences().edit().putStringSet(LARGE_TILES_SPECS_KEY, specs).apply()
+        }
+
+        private fun Kosmos.getLargeTilesSpecsFromSharedPreferences(): Set<String> {
+            return getSharedPreferences().getStringSet(LARGE_TILES_SPECS_KEY, emptySet())!!
+        }
+
+        private fun Set<String>.toTileSpecs(): Set<TileSpec> {
+            return map { TileSpec.create(it) }.toSet()
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
index c775bfd..9e400a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runTest
@@ -34,6 +35,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class GridLayoutTypeInteractorTest : SysuiTestCase() {
     val kosmos = testKosmos()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index 79acfda..9838bcb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -66,7 +66,7 @@
                 runCurrent()
 
                 // Resize it to large
-                qsPreferencesRepository.setLargeTilesSpecs(setOf(spec))
+                qsPreferencesRepository.writeLargeTileSpecs(setOf(spec))
                 runCurrent()
 
                 // Assert that the new tile was added to the large tiles set
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
index 2e7aeb4..9fe783b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.configurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
@@ -76,6 +77,7 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun withDualShade_returnsCorrectValue() =
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
index fdbf42c..d5e502e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
@@ -36,6 +37,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -43,6 +45,7 @@
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
 class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : SysuiTestCase() {
@@ -63,6 +66,7 @@
     }
 
     @Test
+    @EnableSceneContainer
     fun shouldMediaShowInRow() =
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
index 241cdbf..4912c31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.testCase
@@ -88,6 +89,7 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun mediaLocationNull_dualShade_alwaysDualShadeColumns() =
         with(kosmos) {
             testScope.runTest {
@@ -111,6 +113,7 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun mediaLocationQS_dualShade_alwaysDualShadeColumns() =
         with(kosmos) {
             testScope.runTest {
@@ -133,6 +136,7 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() =
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 4b8cd37..d9b3926 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository
@@ -242,9 +243,12 @@
             storeTilesForUser(startingTiles, userId)
 
             val tiles by collectLastValue(underTest.tilesSpecs(userId))
-            val tilesRead by collectLastValue(underTest.tilesReadFromSetting.consumeAsFlow())
+            val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
 
-            assertThat(tilesRead).isEqualTo(startingTiles.toTileSpecs().toSet() to userId)
+            assertThat(tilesRead)
+                .isEqualTo(
+                    TilesUpgradePath.ReadFromSettings(startingTiles.toTileSpecs().toSet()) to userId
+                )
         }
 
     @Test
@@ -258,13 +262,13 @@
             val tiles10 by collectLastValue(underTest.tilesSpecs(10))
             val tiles11 by collectLastValue(underTest.tilesSpecs(11))
 
-            val tilesRead by collectValues(underTest.tilesReadFromSetting.consumeAsFlow())
+            val tilesRead by collectValues(underTest.tilesUpgradePath.consumeAsFlow())
 
             assertThat(tilesRead).hasSize(2)
             assertThat(tilesRead)
                 .containsExactly(
-                    startingTiles10.toTileSpecs().toSet() to 10,
-                    startingTiles11.toTileSpecs().toSet() to 11,
+                    TilesUpgradePath.ReadFromSettings(startingTiles10.toTileSpecs().toSet()) to 10,
+                    TilesUpgradePath.ReadFromSettings(startingTiles11.toTileSpecs().toSet()) to 11,
                 )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index 1945f75..29bd18d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -7,8 +7,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.data.model.RestoreData
-import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepositoryTest.Companion.toTilesSet
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -352,11 +352,11 @@
     @Test
     fun noSettingsStored_noTilesReadFromSettings() =
         testScope.runTest {
-            val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+            val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
             val tiles by collectLastValue(underTest.tiles())
 
             assertThat(tiles).isEqualTo(getDefaultTileSpecs())
-            assertThat(tilesRead).isEqualTo(null)
+            assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
         }
 
     @Test
@@ -365,19 +365,20 @@
             val storedTiles = "a,b"
             storeTiles(storedTiles)
             val tiles by collectLastValue(underTest.tiles())
-            val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+            val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
 
-            assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+            assertThat(tilesRead)
+                .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
         }
 
     @Test
     fun noSettingsStored_tilesChanged_tilesReadFromSettingsNotChanged() =
         testScope.runTest {
-            val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+            val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
             val tiles by collectLastValue(underTest.tiles())
 
             underTest.addTile(TileSpec.create("a"))
-            assertThat(tilesRead).isEqualTo(null)
+            assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
         }
 
     @Test
@@ -386,10 +387,34 @@
             val storedTiles = "a,b"
             storeTiles(storedTiles)
             val tiles by collectLastValue(underTest.tiles())
-            val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+            val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
 
             underTest.addTile(TileSpec.create("c"))
-            assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+            assertThat(tilesRead)
+                .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
+        }
+
+    @Test
+    fun tilesRestoredFromBackup() =
+        testScope.runTest {
+            val specsBeforeRestore = "a,b,c,d,e"
+            val restoredSpecs = "a,c,d,f"
+            val autoAddedBeforeRestore = "b,d"
+            val restoredAutoAdded = "d,e"
+
+            storeTiles(specsBeforeRestore)
+            val tiles by collectLastValue(underTest.tiles())
+            val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
+            runCurrent()
+
+            val restoreData =
+                RestoreData(restoredSpecs.toTileSpecs(), restoredAutoAdded.toTilesSet(), USER)
+            underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet())
+            runCurrent()
+
+            val expected = "a,b,c,d,f"
+            assertThat(tilesRead)
+                .isEqualTo(TilesUpgradePath.RestoreFromBackup(expected.toTilesSet()))
         }
 
     private fun getDefaultTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index adaebbd..5527393 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -25,8 +25,10 @@
 import com.android.compose.animation.scene.DefaultEdgeDetector
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.fakeFalsingManager
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.data.repository.fakePowerRepository
@@ -44,17 +46,15 @@
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -64,10 +64,6 @@
 
     private val kosmos = testKosmos()
     private val testScope by lazy { kosmos.testScope }
-    private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
-    private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
-    private val fakeRemoteInputRepository by lazy { kosmos.fakeRemoteInputRepository }
     private val falsingManager by lazy { kosmos.fakeFalsingManager }
     private val view = mock<View>()
 
@@ -91,14 +87,14 @@
 
     @Test
     fun activate_setsMotionEventHandler() =
-        testScope.runTest {
+        kosmos.runTest {
             runCurrent()
             assertThat(motionEventHandler).isNotNull()
         }
 
     @Test
     fun deactivate_clearsMotionEventHandler() =
-        testScope.runTest {
+        kosmos.runTest {
             activationJob.cancel()
             runCurrent()
 
@@ -107,7 +103,7 @@
 
     @Test
     fun isVisible() =
-        testScope.runTest {
+        kosmos.runTest {
             assertThat(underTest.isVisible).isTrue()
 
             sceneInteractor.setVisible(false, "reason")
@@ -121,7 +117,7 @@
 
     @Test
     fun sceneTransition() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
@@ -132,7 +128,7 @@
 
     @Test
     fun canChangeScene_whenAllowed_switchingFromGone_returnsTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
             fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
             runCurrent()
@@ -149,7 +145,7 @@
 
     @Test
     fun canChangeScene_whenAllowed_switchingFromLockscreen_returnsTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
             runCurrent()
@@ -166,7 +162,7 @@
 
     @Test
     fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingProtectedScenes_returnsFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             falsingManager.setIsFalseTouch(true)
             val currentScene by collectLastValue(underTest.currentScene)
             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
@@ -188,7 +184,7 @@
 
     @Test
     fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingUnprotectedScenes_returnsTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             falsingManager.setIsFalseTouch(true)
             val currentScene by collectLastValue(underTest.currentScene)
             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
@@ -209,7 +205,7 @@
 
     @Test
     fun canChangeScene_whenNotAllowed_fromGone_toAnyOtherScene_returnsTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             falsingManager.setIsFalseTouch(true)
             val currentScene by collectLastValue(underTest.currentScene)
             fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
@@ -226,8 +222,70 @@
         }
 
     @Test
+    fun canShowOrReplaceOverlay_whenAllowed_showingWhileOnGone_returnsTrue() =
+        kosmos.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
+            runCurrent()
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+            sceneContainerConfig.overlayKeys.forEach { overlay ->
+                assertWithMessage("Overlay $overlay incorrectly protected when allowed")
+                    .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+                    .isTrue()
+            }
+        }
+
+    @Test
+    fun canShowOrReplaceOverlay_whenAllowed_showingWhileOnLockscreen_returnsTrue() =
+        kosmos.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
+            runCurrent()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+            sceneContainerConfig.overlayKeys.forEach { overlay ->
+                assertWithMessage("Overlay $overlay incorrectly protected when allowed")
+                    .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+                    .isTrue()
+            }
+        }
+
+    @Test
+    fun canShowOrReplaceOverlay_whenNotAllowed_whileOnLockscreen_returnsFalse() =
+        kosmos.runTest {
+            falsingManager.setIsFalseTouch(true)
+            val currentScene by collectLastValue(underTest.currentScene)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
+            runCurrent()
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+            sceneContainerConfig.overlayKeys.forEach { overlay ->
+                assertWithMessage("Protected overlay $overlay not properly protected")
+                    .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+                    .isFalse()
+            }
+        }
+
+    @Test
+    fun canShowOrReplaceOverlay_whenNotAllowed_whileOnGone_returnsTrue() =
+        kosmos.runTest {
+            falsingManager.setIsFalseTouch(true)
+            val currentScene by collectLastValue(underTest.currentScene)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
+            runCurrent()
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+            sceneContainerConfig.overlayKeys.forEach { overlay ->
+                assertWithMessage("Protected overlay $overlay not properly protected")
+                    .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
+                    .isTrue()
+            }
+        }
+
+    @Test
     fun userInput() =
-        testScope.runTest {
+        kosmos.runTest {
             assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
             underTest.onMotionEvent(mock())
             assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
@@ -235,7 +293,7 @@
 
     @Test
     fun userInputOnEmptySpace_insideEvent() =
-        testScope.runTest {
+        kosmos.runTest {
             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
             val insideMotionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0f, 0f, 0)
             underTest.onEmptySpaceMotionEvent(insideMotionEvent)
@@ -244,7 +302,7 @@
 
     @Test
     fun userInputOnEmptySpace_outsideEvent_remoteInputActive() =
-        testScope.runTest {
+        kosmos.runTest {
             fakeRemoteInputRepository.isRemoteInputActive.value = true
             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
             val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
@@ -254,7 +312,7 @@
 
     @Test
     fun userInputOnEmptySpace_outsideEvent_remoteInputInactive() =
-        testScope.runTest {
+        kosmos.runTest {
             fakeRemoteInputRepository.isRemoteInputActive.value = false
             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
             val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
@@ -264,7 +322,7 @@
 
     @Test
     fun remoteUserInteraction_keepsContainerVisible() =
-        testScope.runTest {
+        kosmos.runTest {
             sceneInteractor.setVisible(false, "reason")
             runCurrent()
             assertThat(underTest.isVisible).isFalse()
@@ -272,9 +330,7 @@
             runCurrent()
             assertThat(underTest.isVisible).isTrue()
 
-            underTest.onMotionEvent(
-                mock { whenever(actionMasked).thenReturn(MotionEvent.ACTION_UP) }
-            )
+            underTest.onMotionEvent(mock { on { actionMasked } doReturn MotionEvent.ACTION_UP })
             runCurrent()
 
             assertThat(underTest.isVisible).isFalse()
@@ -282,7 +338,7 @@
 
     @Test
     fun getActionableContentKey_noOverlays_returnsCurrentScene() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -300,7 +356,7 @@
 
     @Test
     fun getActionableContentKey_multipleOverlays_returnsTopOverlay() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
             fakeSceneDataSource.showOverlay(Overlays.QuickSettingsShade)
@@ -321,7 +377,7 @@
 
     @Test
     fun edgeDetector_singleShade_usesDefaultEdgeDetector() =
-        testScope.runTest {
+        kosmos.runTest {
             val shadeMode by collectLastValue(kosmos.shadeMode)
             kosmos.enableSingleShade()
 
@@ -331,7 +387,7 @@
 
     @Test
     fun edgeDetector_splitShade_usesDefaultEdgeDetector() =
-        testScope.runTest {
+        kosmos.runTest {
             val shadeMode by collectLastValue(kosmos.shadeMode)
             kosmos.enableSplitShade()
 
@@ -341,7 +397,7 @@
 
     @Test
     fun edgeDetector_dualShade_narrowScreen_usesSceneContainerSwipeDetector() =
-        testScope.runTest {
+        kosmos.runTest {
             val shadeMode by collectLastValue(kosmos.shadeMode)
             kosmos.enableDualShade(wideLayout = false)
 
@@ -352,7 +408,7 @@
 
     @Test
     fun edgeDetector_dualShade_wideScreen_usesSceneContainerSwipeDetector() =
-        testScope.runTest {
+        kosmos.runTest {
             val shadeMode by collectLastValue(kosmos.shadeMode)
             kosmos.enableDualShade(wideLayout = true)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index c1477fe..a9f3a65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -80,6 +80,8 @@
         // Set desktop mode supported
         whenever(mContext.resources).thenReturn(mResources)
         whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+        whenever(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops))
+            .thenReturn(true)
 
         policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
index 668f568..d26e195 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
@@ -31,6 +32,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class ShadeModeInteractorImplTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -80,7 +82,7 @@
         }
 
     @Test
-    fun isDualShade_settingEnabled_returnsTrue() =
+    fun isDualShade_settingEnabledSceneContainerEnabled_returnsTrue() =
         testScope.runTest {
             // TODO(b/391578667): Add a test case for user switching once the bug is fixed.
             val shadeMode by collectLastValue(underTest.shadeMode)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index b8f66ac..dde8678 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
@@ -59,6 +60,7 @@
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -103,6 +105,7 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun hydrateShadeMode_dualShadeEnabled() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index dbe8f82..c7b3175 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -265,91 +264,25 @@
         }
 
     @Test
-    fun chip_positiveStartTime_notPromoted_colorsAreThemed() =
+    fun chip_positiveStartTime_colorsAreAccentThemed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null))
 
             assertThat((latest as OngoingActivityChipModel.Active).colors)
-                .isEqualTo(ColorsModel.Themed)
+                .isEqualTo(ColorsModel.AccentThemed)
         }
 
     @Test
-    fun chip_zeroStartTime_notPromoted_colorsAreThemed() =
+    fun chip_zeroStartTime_colorsAreAccentThemed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null))
 
             assertThat((latest as OngoingActivityChipModel.Active).colors)
-                .isEqualTo(ColorsModel.Themed)
-        }
-
-    @Test
-    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
-    fun chip_positiveStartTime_promoted_notifChipsFlagOff_colorsAreThemed() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.chip)
-
-            repo.setOngoingCallState(
-                inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
-            )
-
-            assertThat((latest as OngoingActivityChipModel.Active).colors)
-                .isEqualTo(ColorsModel.Themed)
-        }
-
-    @Test
-    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
-    fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreThemed() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.chip)
-
-            repo.setOngoingCallState(
-                inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
-            )
-
-            assertThat((latest as OngoingActivityChipModel.Active).colors)
-                .isEqualTo(ColorsModel.Themed)
-        }
-
-    @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
-    fun chip_positiveStartTime_promoted_notifChipsFlagOn_colorsAreCustom() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.chip)
-
-            repo.setOngoingCallState(
-                inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
-            )
-
-            assertThat((latest as OngoingActivityChipModel.Active).colors)
-                .isEqualTo(
-                    ColorsModel.Custom(
-                        backgroundColorInt = PROMOTED_BACKGROUND_COLOR,
-                        primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR,
-                    )
-                )
-        }
-
-    @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
-    fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreCustom() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.chip)
-
-            repo.setOngoingCallState(
-                inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR)
-            )
-
-            assertThat((latest as OngoingActivityChipModel.Active).colors)
-                .isEqualTo(
-                    ColorsModel.Custom(
-                        backgroundColorInt = PROMOTED_BACKGROUND_COLOR,
-                        primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR,
-                    )
-                )
+                .isEqualTo(ColorsModel.AccentThemed)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 192ad87..aaa9b58 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -186,7 +186,7 @@
 
     @Test
     @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
-    fun chips_onePromotedNotif_colorMatches() =
+    fun chips_onePromotedNotif_colorIsSystemThemed() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -209,10 +209,7 @@
             )
 
             assertThat(latest).hasSize(1)
-            val colors = latest!![0].colors
-            assertThat(colors).isInstanceOf(ColorsModel.Custom::class.java)
-            assertThat((colors as ColorsModel.Custom).backgroundColorInt).isEqualTo(56)
-            assertThat((colors as ColorsModel.Custom).primaryTextColorInt).isEqualTo(89)
+            assertThat(latest!![0].colors).isEqualTo(ColorsModel.SystemThemed)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
index d727089..9ec5a42 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
@@ -52,24 +52,13 @@
     }
 
     @Test
-    fun shouldShowText_desiredSlightlyLargerThanMax_true() {
+    fun shouldShowText_desiredMoreThanMax_false() {
         val result =
             underTest.shouldShowText(
                 desiredTextWidthPx = (MAX_WIDTH * 1.1).toInt(),
                 widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
             )
 
-        assertThat(result).isTrue()
-    }
-
-    @Test
-    fun shouldShowText_desiredMoreThanTwiceMax_false() {
-        val result =
-            underTest.shouldShowText(
-                desiredTextWidthPx = (MAX_WIDTH * 2.2).toInt(),
-                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
-            )
-
         assertThat(result).isFalse()
     }
 
@@ -80,8 +69,8 @@
                 View.MeasureSpec.makeMeasureSpec(MAX_WIDTH / 2, View.MeasureSpec.AT_MOST)
             )
 
-        // WHEN desired is more than twice the smallerWidthSpec
-        val desiredWidth = (MAX_WIDTH * 1.1).toInt()
+        // WHEN desired is more than the smallerWidthSpec
+        val desiredWidth = ((MAX_WIDTH / 2) * 1.1).toInt()
 
         val result =
             underTest.shouldShowText(
@@ -100,8 +89,8 @@
                 View.MeasureSpec.makeMeasureSpec(MAX_WIDTH * 3, View.MeasureSpec.AT_MOST)
             )
 
-        // WHEN desired is more than twice the max
-        val desiredWidth = (MAX_WIDTH * 2.2).toInt()
+        // WHEN desired is more than the max
+        val desiredWidth = (MAX_WIDTH * 1.1).toInt()
 
         val result =
             underTest.shouldShowText(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index 60030ad..e3a84fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -54,7 +54,7 @@
                 OngoingActivityChipModel.Active.Timer(
                     key = KEY,
                     icon = createIcon(R.drawable.ic_cake),
-                    colors = ColorsModel.Themed,
+                    colors = ColorsModel.AccentThemed,
                     startTimeMs = 100L,
                     onClickListenerLegacy = null,
                     clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
@@ -68,7 +68,7 @@
                 OngoingActivityChipModel.Active.IconOnly(
                     key = KEY,
                     icon = createIcon(R.drawable.ic_hotspot),
-                    colors = ColorsModel.Themed,
+                    colors = ColorsModel.AccentThemed,
                     onClickListenerLegacy = null,
                     clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
                 )
@@ -90,7 +90,7 @@
                 OngoingActivityChipModel.Active.Timer(
                     key = KEY,
                     icon = createIcon(R.drawable.ic_cake),
-                    colors = ColorsModel.Themed,
+                    colors = ColorsModel.AccentThemed,
                     startTimeMs = 100L,
                     onClickListenerLegacy = null,
                     clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
@@ -132,7 +132,7 @@
                 OngoingActivityChipModel.Active.Timer(
                     key = KEY,
                     icon = createIcon(R.drawable.ic_cake),
-                    colors = ColorsModel.Themed,
+                    colors = ColorsModel.AccentThemed,
                     startTimeMs = 100L,
                     onClickListenerLegacy = null,
                     clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 403ac32..20637cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,7 +293,7 @@
 
     @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
     @Test
-    fun chipsLegacy_twoTimerChips_isSmallPortrait_andChipsModernizationDisabled_bothSquished() =
+    fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             addOngoingCallState(key = "call")
@@ -307,6 +307,22 @@
                 .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
         }
 
+    @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    @Test
+    fun chips_twoTimerChips_isSmallPortrait_bothSquished() =
+        kosmos.runTest {
+            screenRecordState.value = ScreenRecordModel.Recording
+            addOngoingCallState(key = "call")
+
+            val latest by collectLastValue(underTest.chips)
+
+            // Squished chips are icon only
+            assertThat(latest!!.active[0])
+                .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+            assertThat(latest!!.active[1])
+                .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+        }
+
     @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
     @Test
     fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
@@ -324,6 +340,23 @@
                 .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
         }
 
+    @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    @Test
+    fun chips_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
+        kosmos.runTest {
+            screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000)
+            addOngoingCallState(key = "call")
+
+            val latest by collectLastValue(underTest.chips)
+
+            // The screen record countdown isn't squished to icon-only
+            assertThat(latest!!.active[0])
+                .isInstanceOf(OngoingActivityChipModel.Active.Countdown::class.java)
+            // But the call chip *is* squished
+            assertThat(latest!!.active[1])
+                .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+        }
+
     @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
     @Test
     fun chipsLegacy_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
@@ -360,6 +393,38 @@
                 .isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
         }
 
+    @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    @Test
+    fun chips_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chips)
+
+            // WHEN there's only one chip
+            screenRecordState.value = ScreenRecordModel.Recording
+            removeOngoingCallState(key = "call")
+
+            // The screen record isn't squished because it's the only one
+            assertThat(latest!!.active[0])
+                .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+
+            // WHEN there's 2 chips
+            addOngoingCallState(key = "call")
+
+            // THEN they both become squished
+            assertThat(latest!!.active[0])
+                .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+            // But the call chip *is* squished
+            assertThat(latest!!.active[1])
+                .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+
+            // WHEN we go back down to 1 chip
+            screenRecordState.value = ScreenRecordModel.DoingNothing
+
+            // THEN the remaining chip unsquishes
+            assertThat(latest!!.active[0])
+                .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+        }
+
     @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
     @Test
     fun chipsLegacy_twoChips_isLandscape_notSquished() =
@@ -383,6 +448,29 @@
                 .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
         }
 
+    @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    @Test
+    fun chips_twoChips_isLandscape_notSquished() =
+        kosmos.runTest {
+            screenRecordState.value = ScreenRecordModel.Recording
+            addOngoingCallState(key = "call")
+
+            // WHEN we're in landscape
+            val config =
+                Configuration(kosmos.mainResources.configuration).apply {
+                    orientation = Configuration.ORIENTATION_LANDSCAPE
+                }
+            kosmos.fakeConfigurationRepository.onConfigurationChange(config)
+
+            val latest by collectLastValue(underTest.chips)
+
+            // THEN the chips aren't squished (squished chips would be icon only)
+            assertThat(latest!!.active[0])
+                .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+            assertThat(latest!!.active[1])
+                .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+        }
+
     @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
     @Test
     fun chipsLegacy_twoChips_isLargeScreen_notSquished() =
@@ -402,16 +490,19 @@
                 .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
         }
 
-    @Test
     @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
-    fun chips_twoChips_chipsModernizationEnabled_notSquished() =
+    @Test
+    fun chips_twoChips_isLargeScreen_notSquished() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             addOngoingCallState(key = "call")
 
+            // WHEN we're on a large screen
+            kosmos.displayStateRepository.setIsLargeScreen(true)
+
             val latest by collectLastValue(underTest.chips)
 
-            // Squished chips would be icon only
+            // THEN the chips aren't squished (squished chips would be icon only)
             assertThat(latest!!.active[0])
                 .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
             assertThat(latest!!.active[1])
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
new file mode 100644
index 0000000..67d0ade
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Flags
+import android.app.Notification
+import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
+import android.app.Person
+import android.content.pm.LauncherApps
+import android.content.pm.launcherApps
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import android.text.SpannableStringBuilder
+import android.text.style.ImageSpan
+import android.text.style.StyleSpan
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.row.notificationRowContentBinderLogger
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class ConversationNotificationProcessorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private lateinit var conversationNotificationProcessor: ConversationNotificationProcessor
+    private lateinit var testHelper: NotificationTestHelper
+    private lateinit var launcherApps: LauncherApps
+    private lateinit var logger: NotificationRowContentBinderLogger
+    private lateinit var conversationNotificationManager: ConversationNotificationManager
+
+    @Before
+    fun setup() {
+        launcherApps = kosmos.launcherApps
+        conversationNotificationManager = kosmos.conversationNotificationManager
+        logger = kosmos.notificationRowContentBinderLogger
+        testHelper = NotificationTestHelper(mContext, mDependency)
+
+        conversationNotificationProcessor =
+            ConversationNotificationProcessor(
+                context,
+                launcherApps,
+                conversationNotificationManager,
+            )
+    }
+
+    @Test
+    fun processNotification_notMessagingStyle() {
+        val nb = Notification.Builder(mContext).setSmallIcon(R.drawable.ic_person)
+        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+
+        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+            .isNull()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NM_SUMMARIZATION, Flags.FLAG_NM_SUMMARIZATION_UI)
+    fun processNotification_messagingStyleWithSummarization_flagOff() {
+        val summarization = "hello"
+        val nb = getMessagingNotification()
+        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+        newRow.entry.setRanking(
+            RankingBuilder(newRow.entry.ranking).setSummarization(summarization).build()
+        )
+
+        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+            .isNotNull()
+        assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+    fun processNotification_messagingStyleWithSummarization() {
+        val summarization = "hello"
+        val nb = getMessagingNotification()
+        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+        newRow.entry.setRanking(
+            RankingBuilder(newRow.entry.ranking).setSummarization(summarization).build()
+        )
+
+        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+            .isNotNull()
+
+        val processedSummary = nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)
+        assertThat(processedSummary.toString()).isEqualTo("x$summarization")
+
+        val checkSpans = SpannableStringBuilder(processedSummary)
+        assertThat(
+                checkSpans.getSpans(
+                    /* queryStart = */ 0,
+                    /* queryEnd = */ 1,
+                    /* kind = */ ImageSpan::class.java,
+                )
+            )
+            .isNotNull()
+
+        assertThat(
+                processedSummary?.let {
+                    checkSpans.getSpans(
+                        /* queryStart = */ 0,
+                        /* queryEnd = */ it.length,
+                        /* kind = */ StyleSpan::class.java,
+                    )
+                }
+            )
+            .isNotNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+    fun processNotification_messagingStyleWithoutSummarization() {
+        val nb = getMessagingNotification()
+        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+            .isNotNull()
+        assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
+    }
+
+    private fun getMessagingNotification(): Notification.Builder {
+        val displayName = "Display Name"
+        val messageText = "Message Text"
+        val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person)
+        val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build()
+        val messagingStyle = Notification.MessagingStyle(testPerson)
+        messagingStyle.addMessage(
+            Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson)
+        )
+        return Notification.Builder(mContext)
+            .setSmallIcon(R.drawable.ic_person)
+            .setStyle(messagingStyle)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index da31cd9..7d406b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -209,6 +209,26 @@
     }
 
     @Test
+    public void testInflationProcessesMessagingStyle() throws Exception {
+        String displayName = "Display Name";
+        String messageText = "Message Text";
+        Icon personIcon = Icon.createWithResource(
+                mContext, com.android.systemui.res.R.drawable.ic_person);
+        Person testPerson = new Person.Builder().setName(displayName).setIcon(personIcon).build();
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(testPerson);
+        messagingStyle.addMessage(new Notification.MessagingStyle.Message(
+                messageText, System.currentTimeMillis(), testPerson));
+        Notification messageNotif = new Notification.Builder(mContext)
+                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+                .setStyle(messagingStyle)
+                .build();
+        ExpandableNotificationRow newRow = mHelper.createRow(messageNotif);
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, newRow);
+
+        verify(mConversationNotificationProcessor).processNotification(any(), any(), any());
+    }
+
+    @Test
     @Ignore
     public void testInflationIsRetriedIfAsyncFails() throws Exception {
         NotificationContentInflater.InflationProgress result =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index c77b09a..82eca37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -587,6 +587,35 @@
         Assert.assertFalse(hasText(publicView, contentTitle))
     }
 
+    @Test
+    @Throws(java.lang.Exception::class)
+    @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION)
+    fun testAllMessagingStyleProcessedAsConversations() {
+        val displayName = "Display Name"
+        val messageText = "Message Text"
+        val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person)
+        val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build()
+        val messagingStyle = Notification.MessagingStyle(testPerson)
+        messagingStyle.addMessage(
+            Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson)
+        )
+        val messageNotif =
+            Notification.Builder(mContext)
+                .setSmallIcon(R.drawable.ic_person)
+                .setStyle(messagingStyle)
+                .build()
+        val newRow: ExpandableNotificationRow = testHelper.createRow(messageNotif)
+
+        inflateAndWait(
+            false,
+            notificationInflater,
+            FLAG_CONTENT_VIEW_ALL,
+            REDACTION_TYPE_NONE,
+            newRow,
+        )
+        verify(conversationNotificationProcessor).processNotification(any(), any(), any())
+    }
+
     private class ExceptionHolder {
         var exception: Exception? = null
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index d570f18..6381b4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -57,11 +57,12 @@
             statusBarStateController = mock()
             whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
         }
-    private val underTest = kosmos.notificationShelfViewModel
     private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
     private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController
     private val powerRepository = kosmos.fakePowerRepository
+    private val keyguardTransitionController by lazy { kosmos.lockscreenShadeTransitionController }
+
+    private val underTest by lazy { kosmos.notificationShelfViewModel }
 
     @Test
     fun canModifyColorOfNotifications_whenKeyguardNotShowing() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 57b7df7..31f8590 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -51,11 +51,13 @@
 import com.android.systemui.shade.ShadeViewStateProvider
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.core.NewStatusBarIcons
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
 import com.android.systemui.statusbar.layout.mockStatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -85,6 +87,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
+@DisableFlags(NewStatusBarIcons.FLAG_NAME)
 class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
     private lateinit var kosmos: Kosmos
     private lateinit var testScope: TestScope
@@ -190,6 +193,7 @@
             statusBarIconController,
             iconManagerFactory,
             batteryMeterViewController,
+            kosmos.batteryViewModelFactory,
             shadeViewStateProvider,
             keyguardStateController,
             keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 4759c08..183cd8f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -19,6 +19,9 @@
 import android.graphics.Color
 import android.graphics.Rect
 import android.view.View
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -26,15 +29,20 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
 import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import org.mockito.Mockito.mock
 
 class FakeHomeStatusBarViewModel(
     override val operatorNameViewModel: StatusBarOperatorNameViewModel
-) : HomeStatusBarViewModel {
+) : HomeStatusBarViewModel, ExclusiveActivatable() {
+    private val hydrator = Hydrator("FakeHomeStatusBarViewModel.hydrator")
+
     override val areNotificationsLightsOut = MutableStateFlow(false)
 
     override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
@@ -56,6 +64,11 @@
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
+    override val batteryViewModelFactory: BatteryViewModel.Factory =
+        object : BatteryViewModel.Factory {
+            override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
+        }
+
     override val shouldShowOperatorNameView = MutableStateFlow(false)
 
     override val isClockVisible =
@@ -80,6 +93,7 @@
 
     var darkIconTint = Color.BLACK
     var lightIconTint = Color.WHITE
+    var darkIntensity = 0f
 
     override val areaTint: Flow<StatusBarTintColor> =
         MutableStateFlow(
@@ -91,4 +105,22 @@
                 }
             }
         )
+
+    val isAreaDarkSource =
+        MutableStateFlow(
+            IsAreaDark { viewBounds ->
+                if (DarkIconDispatcher.isInAreas(darkRegions, viewBounds)) {
+                    darkIntensity < 0.5f
+                } else {
+                    false
+                }
+            }
+        )
+
+    override val areaDark: IsAreaDark by
+        hydrator.hydratedStateOf(traceName = "areaDark", source = isAreaDarkSource)
+
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 354edac..36e18e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -56,7 +56,6 @@
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
 import com.android.systemui.unfoldedDeviceState
 import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -79,8 +78,10 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -603,12 +604,39 @@
         }
     }
 
+    @Test
+    fun foldingStarted_screenStillOn_eventSentOnlyAfterScreenSwitches() {
+        // can happen for both folding and unfolding (with animations off) but it's more likely to
+        // happen when folding as waiting for screen on is the default case then
+        testScope.runTest {
+            startInUnfoldedState(displaySwitchLatencyTracker)
+            setDeviceState(FOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
+            runCurrent()
+
+            verifyNoMoreInteractions(displaySwitchLatencyLogger)
+
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
+            runCurrent()
+            powerInteractor.setScreenPowerState(SCREEN_ON)
+            runCurrent()
+
+            verify(displaySwitchLatencyLogger).log(any())
+        }
+    }
+
     private suspend fun TestScope.startInFoldedState(tracker: DisplaySwitchLatencyTracker) {
         setDeviceState(FOLDED)
         tracker.start()
         runCurrent()
     }
 
+    private suspend fun TestScope.startInUnfoldedState(tracker: DisplaySwitchLatencyTracker) {
+        setDeviceState(UNFOLDED)
+        tracker.start()
+        runCurrent()
+    }
+
     private suspend fun TestScope.startUnfolding() {
         setDeviceState(HALF_FOLDED)
         powerInteractor.setScreenPowerState(SCREEN_OFF)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 75f3386..b8e1924 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -47,6 +47,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.TestScopeProvider;
+import com.android.settingslib.volume.MediaSessions;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestCaseExtKt;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -268,13 +269,15 @@
     @Test
     public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
         MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
-        mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
+        var sessionId = MediaSessions.SessionId.Companion.from(token);
+        mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(sessionId, 0);
     }
 
     @Test
     public void testOnRemoteRemove_newStream_noNullPointer() {
         MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
-        mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
+        var sessionId = MediaSessions.SessionId.Companion.from(token);
+        mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(sessionId);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
index 390518f..a023b3c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.testKosmos
+import com.android.systemui.window.data.repository.fakeWindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.windowRootViewBlurRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -34,7 +36,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+@EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
 class WindowRootViewModelTest : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
@@ -49,6 +51,7 @@
     @Test
     fun bouncerTransitionChangesWindowBlurRadius() =
         testScope.runTest {
+            kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = true
             val blurRadius by collectLastValue(underTest.blurRadius)
             val isBlurOpaque by collectLastValue(underTest.isBlurOpaque)
             runCurrent()
@@ -59,4 +62,27 @@
             assertThat(blurRadius).isEqualTo(30)
             assertThat(isBlurOpaque).isEqualTo(false)
         }
+
+    @Test
+    fun blurRadiusDoesNotChangeWhenBlurIsNotSupported() =
+        testScope.runTest {
+            kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = false
+            val blurRadius by collectLastValue(underTest.blurRadius)
+            runCurrent()
+
+            kosmos.fakeBouncerTransitions.first().windowBlurRadius.value = 30.0f
+            runCurrent()
+
+            assertThat(blurRadius).isEqualTo(0f)
+
+            kosmos.fakeGlanceableHubTransitions.first().windowBlurRadius.value = 50.0f
+            runCurrent()
+
+            assertThat(blurRadius).isEqualTo(0f)
+
+            kosmos.windowRootViewBlurRepository.blurRadius.value = 60
+            runCurrent()
+
+            assertThat(blurRadius).isEqualTo(0f)
+        }
 }
diff --git a/packages/SystemUI/plugin_core/proguard.flags b/packages/SystemUI/plugin_core/proguard.flags
index 6240898..8b78ba4 100644
--- a/packages/SystemUI/plugin_core/proguard.flags
+++ b/packages/SystemUI/plugin_core/proguard.flags
@@ -8,4 +8,7 @@
 -keep interface com.android.systemui.plugins.annotations.** {
     *;
 }
--keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class *
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class * {
+    void <init>();
+}
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index e152c98..2b908a7 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -12,8 +12,14 @@
 # Note that we restrict this to SysUISingleton classes, as other registering
 # classes should either *always* unregister or *never* register from their
 # constructor. We also keep callback class names for easier debugging.
--keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
--keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class * {
+  void <init>();
+}
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** {
+  void <init>();
+}
 -if @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
 -keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
   <1> *;
@@ -23,10 +29,16 @@
   <1> *;
 }
 
--keep class androidx.core.app.CoreComponentFactory
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep class androidx.core.app.CoreComponentFactory {
+  void <init>();
+}
 
 # Keep the wm shell lib
--keep class com.android.wm.shell.*
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep class com.android.wm.shell.* {
+  void <init>();
+}
 # Keep the protolog group methods that are called by the generated code
 -keepclassmembers class com.android.wm.shell.protolog.ShellProtoLogGroup {
     *;
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
new file mode 100644
index 0000000..1ba637f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<inset
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/clipboard_minimized_background"
+    android:inset="4dp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 448b3e7..915563b 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -171,12 +171,12 @@
         android:layout_height="wrap_content"
         android:visibility="gone"
         android:elevation="7dp"
-        android:padding="8dp"
+        android:padding="12dp"
         app:layout_constraintBottom_toTopOf="@id/indication_container"
         app:layout_constraintStart_toStartOf="parent"
-        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
-        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
-        android:background="@drawable/clipboard_minimized_background">
+        android:layout_marginStart="4dp"
+        android:layout_marginBottom="2dp"
+        android:background="@drawable/clipboard_minimized_background_inset">
         <ImageView
             android:src="@drawable/ic_content_paste"
             android:tint="?attr/overlayButtonTextColor"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index b9ef88e..32407c6 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -147,6 +147,7 @@
                 android:id="@+id/batteryRemainingIcon"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:visibility="gone"
                 app:textAppearance="@style/TextAppearance.QS.Status" />
         </LinearLayout>
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml b/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
index 6f42286..b66a88a 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip_content.xml
@@ -43,9 +43,6 @@
              ongoing_activity_chip_short_time_delta] will ever be shown at one time. -->
 
         <!-- Shows a timer, like 00:01. -->
-        <!-- Don't use the LimitedWidth style for the timer because the end of the timer is often
-             the most important value. ChipChronometer has the correct logic for when the timer is
-             too large for the space allowed. -->
         <com.android.systemui.statusbar.chips.ui.view.ChipChronometer
             android:id="@+id/ongoing_activity_chip_time"
             style="@style/StatusBar.Chip.Text"
@@ -54,14 +51,14 @@
         <!-- Shows generic text. -->
         <com.android.systemui.statusbar.chips.ui.view.ChipTextView
             android:id="@+id/ongoing_activity_chip_text"
-            style="@style/StatusBar.Chip.Text.LimitedWidth"
+            style="@style/StatusBar.Chip.Text"
             android:visibility="gone"
             />
 
         <!-- Shows a time delta in short form, like "15min" or "1hr". -->
         <com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
             android:id="@+id/ongoing_activity_chip_short_time_delta"
-            style="@style/StatusBar.Chip.Text.LimitedWidth"
+            style="@style/StatusBar.Chip.Text"
             android:visibility="gone"
             />
 
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index c28dc50..bb99d58 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -34,12 +34,17 @@
         android:orientation="horizontal"/>
 
     <!-- PaddingEnd is added to balance hover padding, compensating for paddingStart in statusIcons.
-         See b/339589733 -->
+         See b/339589733.
+
+         Default visibility is now "gone" to make space for the new battery icon
+         -->
     <com.android.systemui.battery.BatteryMeterView android:id="@+id/battery"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:clipToPadding="false"
         android:clipChildren="false"
         android:paddingEnd="@dimen/status_bar_battery_end_padding"
+        android:visibility="gone"
         systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
+
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 67f620f..8ad99ab 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,7 +16,7 @@
 <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/volume_dialog_root"
+    android:id="@+id/volume_dialog"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:alpha="0"
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
index 6748cfa..4e3c8cc 100644
--- a/packages/SystemUI/res/layout/volume_ringer_button.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -13,20 +13,13 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content" >
-
-    <ImageButton
-        android:id="@+id/volume_drawer_button"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
-        android:contentDescription="@string/volume_ringer_mode"
-        android:gravity="center"
-        android:tint="@androidprv:color/materialColorOnSurface"
-        android:src="@drawable/volume_ringer_item_bg"
-        android:background="@drawable/volume_ringer_item_bg"/>
-
-</FrameLayout>
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/volume_ringer_item_bg"
+    android:contentDescription="@string/volume_ringer_mode"
+    android:gravity="center"
+    android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+    android:src="@drawable/volume_ringer_item_bg"
+    android:tint="@androidprv:color/materialColorOnSurface" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9b8926e..09aa224 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1110,4 +1110,7 @@
     <!-- Configuration for wallpaper focal area -->
     <bool name="center_align_focal_area_shape">false</bool>
     <string name="focal_area_target" translatable="false" />
+
+    <!-- Configuration to swipe to open glanceable hub -->
+    <bool name="config_swipeToOpenGlanceableHub">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2d3c07b..648e4c2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1811,6 +1811,7 @@
     <dimen name="ongoing_activity_chip_text_end_padding_for_embedded_padding_icon">6dp</dimen>
     <dimen name="ongoing_activity_chip_text_fading_edge_length">12dp</dimen>
     <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
+    <dimen name="ongoing_activity_chip_outline_width">2px</dimen>
 
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d18a90a..8629203 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1351,6 +1351,10 @@
     <string name="accessibility_action_label_shrink_widget">Decrease height</string>
     <!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
     <string name="accessibility_action_label_expand_widget">Increase height</string>
+    <!-- Label for accessibility action to show the next media player. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_action_label_umo_show_next">Show next</string>
+    <!-- Label for accessibility action to show the previous media player. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_action_label_umo_show_previous">Show previous</string>
     <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
     <string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
     <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7f2c893..4961a7e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -93,15 +93,6 @@
         <item name="android:textColor">?android:attr/colorPrimary</item>
     </style>
 
-    <!-- Style for a status bar chip text that has a maximum width. Since there's so little room in
-         the status bar chip area, don't ellipsize the text and instead just fade it out a bit at
-         the end. -->
-    <style name="StatusBar.Chip.Text.LimitedWidth">
-        <item name="android:ellipsize">none</item>
-        <item name="android:requiresFadingEdge">horizontal</item>
-        <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item>
-    </style>
-
     <style name="Chipbar" />
 
     <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 335a910..73dc282 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -98,7 +98,6 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Flags;
 import com.android.systemui.FontStyles;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -121,6 +120,7 @@
     static final int USER_TYPE_PRIMARY = 1;
     static final int USER_TYPE_WORK_PROFILE = 2;
     static final int USER_TYPE_SECONDARY_USER = 3;
+    private boolean mTransparentModeEnabled = false;
 
     @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
     public @interface Mode {}
@@ -814,15 +814,30 @@
         mDisappearAnimRunning = false;
     }
 
+    /**
+     * Make the bouncer background transparent
+     */
+    public void enableTransparentMode() {
+        mTransparentModeEnabled = true;
+        reloadBackgroundColor();
+    }
+
+    /**
+     * Make the bouncer background opaque
+     */
+    public void disableTransparentMode() {
+        mTransparentModeEnabled = false;
+        reloadBackgroundColor();
+    }
+
     private void reloadBackgroundColor() {
-        if (Flags.bouncerUiRevamp()) {
-            // Keep the background transparent, otherwise the background color looks like a box
-            // while scaling the bouncer for back animation or while transitioning to the bouncer.
+        if (mTransparentModeEnabled) {
             setBackgroundColor(Color.TRANSPARENT);
         } else {
             setBackgroundColor(
                     getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim));
         }
+        invalidate();
     }
 
     void reloadColors() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ff7b2b0..d10fce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -70,6 +70,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -96,6 +97,8 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository;
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
 
 import dagger.Lazy;
 
@@ -134,6 +137,7 @@
     private final FalsingA11yDelegate mFalsingA11yDelegate;
     private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
+    private final Lazy<WindowRootViewBlurInteractor> mRootViewBlurInteractor;
     private int mTranslationY;
     private final KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -431,6 +435,7 @@
     private final Executor mBgExecutor;
     @Nullable
     private Job mSceneTransitionCollectionJob;
+    private Job mBlurEnabledCollectionJob;
 
     @Inject
     public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -463,9 +468,11 @@
             KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
             Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             @Background Executor bgExecutor,
-            Provider<DeviceEntryInteractor> deviceEntryInteractor
+            Provider<DeviceEntryInteractor> deviceEntryInteractor,
+            Lazy<WindowRootViewBlurInteractor> rootViewBlurInteractorProvider
     ) {
         super(view);
+        mRootViewBlurInteractor = rootViewBlurInteractorProvider;
         view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
         mLockPatternUtils = lockPatternUtils;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -539,6 +546,32 @@
                 }
             );
         }
+
+        if (Flags.bouncerUiRevamp()) {
+            mBlurEnabledCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+                    mRootViewBlurInteractor.get().isBlurCurrentlySupported(),
+                    this::handleBlurSupportedChanged);
+        }
+    }
+
+    private void handleBlurSupportedChanged(boolean isWindowBlurSupported) {
+        if (isWindowBlurSupported) {
+            mView.enableTransparentMode();
+        } else {
+            mView.disableTransparentMode();
+        }
+    }
+
+    private void refreshBouncerBackground() {
+        // This is present solely for screenshot tests that disable blur by invoking setprop to
+        // disable blurs, however the mRootViewBlurInteractor#isBlurCurrentlySupported doesn't emit
+        // an updated value because sysui doesn't have a way to register for changes to setprop.
+        // KeyguardSecurityContainer view is inflated only once and doesn't re-inflate so it has to
+        // check the sysprop every time bouncer is about to be shown.
+        if (Flags.bouncerUiRevamp() && (ActivityManager.isRunningInUserTestHarness()
+                || ActivityManager.isRunningInTestHarness())) {
+            handleBlurSupportedChanged(!WindowRootViewBlurRepository.isDisableBlurSysPropSet());
+        }
     }
 
     @Override
@@ -552,6 +585,11 @@
             mSceneTransitionCollectionJob.cancel(null);
             mSceneTransitionCollectionJob = null;
         }
+
+        if (mBlurEnabledCollectionJob != null) {
+            mBlurEnabledCollectionJob.cancel(null);
+            mBlurEnabledCollectionJob = null;
+        }
     }
 
     /**  */
@@ -718,6 +756,8 @@
         if (bouncerUserSwitcher != null) {
             bouncerUserSwitcher.setAlpha(0f);
         }
+
+        refreshBouncerBackground();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a67ec65..8734d05 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -296,6 +296,7 @@
         mGestureDetector =
                 new MagnificationGestureDetector(mContext, handler, this);
         mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+        mWindowInsetChangeRunnable.run();
 
         // Initialize listeners.
         mMirrorViewRunnable = new Runnable() {
@@ -367,8 +368,12 @@
     private boolean updateSystemGestureInsetsTop() {
         final WindowMetrics windowMetrics = mWm.getCurrentWindowMetrics();
         final Insets insets = windowMetrics.getWindowInsets().getInsets(systemGestures());
-        final int gestureTop =
-                insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
+        final int gestureTop;
+        if (Flags.updateWindowMagnifierBottomBoundary()) {
+            gestureTop = windowMetrics.getBounds().bottom - insets.bottom;
+        } else {
+            gestureTop = insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
+        }
         if (gestureTop != mSystemGestureTop) {
             mSystemGestureTop = gestureTop;
             return true;
@@ -953,7 +958,6 @@
                 ? mSystemGestureTop - height + mOuterBorderSize
                 : mWindowBounds.bottom - height + mOuterBorderSize;
         final int y = MathUtils.clamp(mMagnificationFrame.top - mMirrorSurfaceMargin, minY, maxY);
-
         if (computeWindowSize) {
             LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
             params.width = width;
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 9a30c21..fcf5105 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -46,7 +46,10 @@
 
 import javax.inject.Inject;
 
-/** Controller for {@link BatteryMeterView}. **/
+/**
+ * Controller for {@link BatteryMeterView}.
+ * @deprecated once [NewStatusBarIcons] is rolled out, this class is no longer needed
+ */
 public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
     private final ConfigurationController mConfigurationController;
     private final TunerService mTunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index fc589b2..e36e855 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -19,11 +19,8 @@
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -31,36 +28,24 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dock.DockManager
 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.TransitionStep
 import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.kotlin.getValue
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import com.android.systemui.util.settings.SystemSettings
-import java.util.Optional
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.withContext
 
@@ -72,16 +57,12 @@
 class CommunalSceneStartable
 @Inject
 constructor(
-    private val dockManager: DockManager,
     private val communalInteractor: CommunalInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val systemSettings: SystemSettings,
-    centralSurfacesOpt: Optional<CentralSurfaces>,
     private val notificationShadeWindowController: NotificationShadeWindowController,
-    @Application private val applicationScope: CoroutineScope,
     @Background private val bgScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val uiEventLogger: UiEventLogger,
@@ -92,110 +73,11 @@
 
     private var isDreaming: Boolean = false
 
-    private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt
-
     override fun start() {
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
             return
         }
 
-        if (!communalSceneKtfRefactor()) {
-            // Handle automatically switching based on keyguard state.
-            keyguardTransitionInteractor.startedKeyguardTransitionStep
-                .mapLatest(::determineSceneAfterTransition)
-                .filterNotNull()
-                .onEach { (nextScene, nextTransition) ->
-                    // When launching a widget, we don't want to animate the scene change or the
-                    // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
-                    // snap to the new scene as part of the launch animation, once the activity
-                    // launch is done, so we don't change scene here.
-                    val delaySceneTransition =
-                        communalSceneInteractor.editModeState.value == EditModeState.STARTING ||
-                            communalSceneInteractor.isLaunchingWidget.value
-                    if (!delaySceneTransition) {
-                        communalSceneInteractor.changeScene(
-                            newScene = nextScene,
-                            loggingReason = "KTF syncing",
-                            transitionKey = nextTransition,
-                        )
-                    }
-                }
-                .launchIn(applicationScope)
-        }
-
-        // TODO(b/322787129): re-enable once custom animations are in place
-        // Handle automatically switching to communal when docked.
-        //        dockManager
-        //            .retrieveIsDocked()
-        //            // Allow some time after docking to ensure the dream doesn't start. If the
-        // dream
-        //            // starts, then we don't want to automatically transition to glanceable hub.
-        //            .debounce(DOCK_DEBOUNCE_DELAY)
-        //            .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
-        //            .onEach { (docked, lastStartedState) ->
-        //                if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
-        //                    communalInteractor.onSceneChanged(CommunalScenes.Communal)
-        //                }
-        //            }
-        //            .launchIn(bgScope)
-
-        systemSettings
-            .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
-            // Read the setting value on start.
-            .emitOnStart()
-            .onEach {
-                screenTimeout =
-                    systemSettings.getIntForUser(
-                        Settings.System.SCREEN_OFF_TIMEOUT,
-                        DEFAULT_SCREEN_TIMEOUT,
-                        UserHandle.USER_CURRENT,
-                    )
-            }
-            .launchIn(bgScope)
-
-        // The hub mode timeout should start as soon as the user enters hub mode. At the end of the
-        // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
-        // dream is not running, nothing will happen. However if the dream starts again underneath
-        // hub mode after the initial timeout expires, such as if the device is docked or the dream
-        // app is updated by the Play store, a new timeout should be started.
-        bgScope.launch {
-            combine(
-                    communalSceneInteractor.currentScene,
-                    // Emit a value on start so the combine starts.
-                    communalInteractor.userActivity.emitOnStart(),
-                ) { scene, _ ->
-                    // Only timeout if we're on the hub is open.
-                    scene.isCommunal()
-                }
-                .collectLatest { shouldTimeout ->
-                    cancelHubTimeout()
-                    if (shouldTimeout) {
-                        startHubTimeout()
-                    }
-                }
-        }
-        bgScope.launch {
-            keyguardInteractor.isDreaming
-                .sample(communalSceneInteractor.currentScene, ::Pair)
-                .collectLatest { (isDreaming, scene) ->
-                    this@CommunalSceneStartable.isDreaming = isDreaming
-                    if (scene.isCommunal() && isDreaming && timeoutJob == null) {
-                        // If dreaming starts after timeout has expired, ex. if dream restarts under
-                        // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub. The
-                        // delay is necessary so the KeyguardInteractor.isAbleToDream flow passes
-                        // through that same amount of delay and publishes a new value which is then
-                        // picked up by the HomeSceneFamilyResolver such that the next call to
-                        // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream".
-                        delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
-                        communalSceneInteractor.changeScene(
-                            CommunalScenes.Blank,
-                            "dream started after timeout",
-                        )
-                        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
-                    }
-                }
-        }
-
         bgScope.launch {
             communalSceneInteractor.isIdleOnCommunal.collectLatest {
                 withContext(mainDispatcher) {
@@ -203,6 +85,75 @@
                 }
             }
         }
+
+        // In V2, the timeout is handled by PowerManagerService since we no longer keep the dream
+        // active underneath the hub.
+        if (!communalSettingsInteractor.isV2FlagEnabled()) {
+            systemSettings
+                .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+                // Read the setting value on start.
+                .emitOnStart()
+                .onEach {
+                    screenTimeout =
+                        systemSettings.getIntForUser(
+                            Settings.System.SCREEN_OFF_TIMEOUT,
+                            DEFAULT_SCREEN_TIMEOUT,
+                            UserHandle.USER_CURRENT,
+                        )
+                }
+                .launchIn(bgScope)
+
+            // The hub mode timeout should start as soon as the user enters hub mode. At the end of
+            // the
+            // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
+            // dream is not running, nothing will happen. However if the dream starts again
+            // underneath
+            // hub mode after the initial timeout expires, such as if the device is docked or the
+            // dream
+            // app is updated by the Play store, a new timeout should be started.
+            bgScope.launch {
+                combine(
+                        communalSceneInteractor.currentScene,
+                        // Emit a value on start so the combine starts.
+                        communalInteractor.userActivity.emitOnStart(),
+                    ) { scene, _ ->
+                        // Only timeout if we're on the hub is open.
+                        scene.isCommunal()
+                    }
+                    .collectLatest { shouldTimeout ->
+                        cancelHubTimeout()
+                        if (shouldTimeout) {
+                            startHubTimeout()
+                        }
+                    }
+            }
+
+            bgScope.launch {
+                keyguardInteractor.isDreaming
+                    .sample(communalSceneInteractor.currentScene, ::Pair)
+                    .collectLatest { (isDreaming, scene) ->
+                        this@CommunalSceneStartable.isDreaming = isDreaming
+                        if (scene.isCommunal() && isDreaming && timeoutJob == null) {
+                            // If dreaming starts after timeout has expired, ex. if dream restarts
+                            // under
+                            // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub.
+                            // The
+                            // delay is necessary so the KeyguardInteractor.isAbleToDream flow
+                            // passes
+                            // through that same amount of delay and publishes a new value which is
+                            // then
+                            // picked up by the HomeSceneFamilyResolver such that the next call to
+                            // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream".
+                            delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+                            communalSceneInteractor.changeScene(
+                                CommunalScenes.Blank,
+                                "dream started after timeout",
+                            )
+                            uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
+                        }
+                    }
+            }
+        }
     }
 
     private fun cancelHubTimeout() {
@@ -231,49 +182,7 @@
         }
     }
 
-    private suspend fun determineSceneAfterTransition(
-        lastStartedTransition: TransitionStep
-    ): Pair<SceneKey, TransitionKey>? {
-        val to = lastStartedTransition.to
-        val from = lastStartedTransition.from
-        val docked = dockManager.isDocked
-        val launchingActivityOverLockscreen =
-            centralSurfaces?.isLaunchingActivityOverLockscreen ?: false
-
-        return when {
-            to == KeyguardState.OCCLUDED && !launchingActivityOverLockscreen -> {
-                // Hide communal when an activity is started on keyguard, to ensure the activity
-                // underneath the hub is shown. When launching activities over lockscreen, we only
-                // change scenes once the activity launch animation is finished, so avoid
-                // changing the scene here.
-                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
-            }
-            to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
-                // When transitioning to the hub from an occluded state, fade out the hub without
-                // doing any translation.
-                Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
-            }
-            // Transitioning to Blank scene when entering the edit mode will be handled separately
-            // with custom animations.
-            to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
-                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
-            !docked && !KeyguardState.deviceIsAwakeInState(to) -> {
-                // If the user taps the screen and wakes the device within this timeout, we don't
-                // want to dismiss the hub
-                delay(AWAKE_DEBOUNCE_DELAY)
-                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
-            }
-            from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
-                // Make sure the communal hub is showing when transitioning from dozing to hub.
-                Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
-            }
-            else -> null
-        }
-    }
-
     companion object {
-        val AWAKE_DEBOUNCE_DELAY = 5.seconds
-        val DOCK_DEBOUNCE_DELAY = 1.seconds
         val DEFAULT_SCREEN_TIMEOUT = 15000
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index f01a6db..ff74162 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -104,6 +104,7 @@
     companion object {
         const val LOGGABLE_PREFIXES = "loggable_prefixes"
         const val LAUNCHER_PACKAGE = "launcher_package"
+        const val SWIPE_TO_HUB = "swipe_to_hub"
 
         @Provides
         @Communal
@@ -143,5 +144,11 @@
         fun provideLauncherPackage(@Main resources: Resources): String {
             return resources.getString(R.string.launcher_overlayable_package)
         }
+
+        @Provides
+        @Named(SWIPE_TO_HUB)
+        fun provideSwipeToHub(@Main resources: Resources): Boolean {
+            return resources.getBoolean(R.bool.config_swipeToOpenGlanceableHub)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index ec55401..740555f4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.data.repository.CommunalSceneTransitionRepository
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
@@ -44,7 +44,6 @@
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * This class listens to [SceneTransitionLayout] transitions and manages keyguard transition
@@ -122,11 +121,7 @@
             )
 
     override fun start() {
-        if (
-            communalSceneKtfRefactor() &&
-                settingsInteractor.isCommunalFlagEnabled() &&
-                !SceneContainerFlag.isEnabled
-        ) {
+        if (settingsInteractor.isCommunalFlagEnabled() && !SceneContainerFlag.isEnabled) {
             sceneInteractor.registerSceneStateProcessor(this)
             listenForSceneTransitionProgress()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 49003a7..d061a33 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -153,7 +153,7 @@
     ) {}
 
     /** Called as the UI requests deleting a widget. */
-    open fun onDeleteWidget(id: Int, componentName: ComponentName, rank: Int) {}
+    open fun onDeleteWidget(id: Int, key: String, componentName: ComponentName, rank: Int) {}
 
     /** Called as the UI detects a tap event on the widget. */
     open fun onTapWidget(componentName: ComponentName, rank: Int) {}
@@ -202,6 +202,12 @@
     /** Called as the user request to show the customize widget button. */
     open fun onLongClick() {}
 
+    /** Called as the user requests to switch to the previous player in UMO. */
+    open fun onShowPreviousMedia() {}
+
+    /** Called as the user requests to switch to the next player in UMO. */
+    open fun onShowNextMedia() {}
+
     /** Called as the UI determines that a new widget has been added to the grid. */
     open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 8aba111..59beb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -141,7 +141,10 @@
         metricsLogger.logAddWidget(componentName.flattenToString(), rank)
     }
 
-    override fun onDeleteWidget(id: Int, componentName: ComponentName, rank: Int) {
+    override fun onDeleteWidget(id: Int, key: String, componentName: ComponentName, rank: Int) {
+        if (selectedKey.value == key) {
+            setSelectedKey(null)
+        }
         communalInteractor.deleteWidget(id)
         metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
     }
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 4bc4400..2169881 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
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SWIPE_TO_HUB
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -92,6 +93,7 @@
     private val metricsLogger: CommunalMetricsLogger,
     mediaCarouselController: MediaCarouselController,
     blurConfig: BlurConfig,
+    @Named(SWIPE_TO_HUB) private val swipeToHub: Boolean,
 ) :
     BaseCommunalViewModel(
         communalSceneInteractor,
@@ -254,6 +256,14 @@
         }
     }
 
+    override fun onShowPreviousMedia() {
+        mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(-1)
+    }
+
+    override fun onShowNextMedia() {
+        mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(1)
+    }
+
     override fun onTapWidget(componentName: ComponentName, rank: Int) {
         metricsLogger.logTapWidget(componentName.flattenToString(), rank)
     }
@@ -349,6 +359,8 @@
     /** See [CommunalSettingsInteractor.isV2FlagEnabled] */
     fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
 
+    fun swipeToHubEnabled(): Boolean = swipeToHub
+
     companion object {
         const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3c68e3a..8bff090 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -74,6 +74,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
+import com.android.systemui.statusbar.notification.dagger.NotificationStackModule;
 import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpModule;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -169,6 +170,7 @@
         WallpaperModule.class,
         ShortcutHelperModule.class,
         ContextualEducationModule.class,
+        NotificationStackModule.class,
 })
 public abstract class ReferenceSystemUIModule {
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index 9607053..b712fde 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -39,6 +39,8 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flow
@@ -99,6 +101,25 @@
         waitForDeviceConnection(deviceType)
     }
 
+    // This flow is used by the notification updater once an initial notification is launched. It
+    // listens to the device connection changes for both keyboard and touchpad. When either of the
+    // device is disconnected, resolve the tutorial type base on the latest connection state.
+    // Dropping the initial state because it's the existing notification. Filtering out BOTH because
+    // we only care about disconnections.
+    val tutorialTypeUpdates: Flow<TutorialType> =
+        keyboardRepository.isAnyKeyboardConnected
+            .combine(touchpadRepository.isAnyTouchpadConnected, ::Pair)
+            .map { (keyboardConnected, touchpadConnected) ->
+                when {
+                    keyboardConnected && touchpadConnected -> TutorialType.BOTH
+                    keyboardConnected -> TutorialType.KEYBOARD
+                    touchpadConnected -> TutorialType.TOUCHPAD
+                    else -> TutorialType.NONE
+                }
+            }
+            .drop(1)
+            .filter { it != TutorialType.BOTH }
+
     private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
         isAnyDeviceConnected[deviceType]!!.filter { it }.first()
 
@@ -172,6 +193,7 @@
                         pw.println(
                             "         launch time = ${repo.getScheduledTutorialLaunchTime(TOUCHPAD)}"
                         )
+                        pw.println("Delay time = ${LAUNCH_DELAY.seconds} sec")
                     }
                 "notify" -> {
                     if (args.size != 2) help(pw)
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
index 3cba70e..a90c7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -42,6 +42,9 @@
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.merge
 
 /** When the scheduler is due, show a notification to launch tutorial */
@@ -55,19 +58,43 @@
     private val notificationManager: NotificationManager,
     private val userTracker: UserTracker,
 ) {
+    private var updaterJob: Job? = null
+
     fun start() {
         backgroundScope.launch {
             merge(
                     tutorialSchedulerInteractor.tutorials,
                     tutorialSchedulerInteractor.commandTutorials,
                 )
-                .collect { showNotification(it) }
+                .filter { it != TutorialType.NONE }
+                .collectLatest {
+                    showNotification(it)
+                    updaterJob?.cancel()
+                    updaterJob = backgroundScope.launch { updateWhenDeviceDisconnects() }
+                }
         }
     }
 
+    private suspend fun updateWhenDeviceDisconnects() {
+        // Only update the notification when there is an active one (i.e. if the notification has
+        // been dismissed by the user, or if the tutorial has been launched, there's no need to
+        // update)
+        tutorialSchedulerInteractor.tutorialTypeUpdates
+            .filter { hasNotification() }
+            .collect {
+                if (it == TutorialType.NONE)
+                    notificationManager.cancelAsUser(TAG, NOTIFICATION_ID, userTracker.userHandle)
+                else showNotification(it)
+            }
+    }
+
+    private fun hasNotification() =
+        notificationManager.activeNotifications.any { it.id == NOTIFICATION_ID }
+
     // By sharing the same tag and id, we update the content of existing notification instead of
     // creating multiple notifications
     private fun showNotification(tutorialType: TutorialType) {
+        // Safe guard - but this should never been reached
         if (tutorialType == TutorialType.NONE) return
 
         if (notificationManager.getNotificationChannel(CHANNEL_ID) == null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
index fdb80b2..978b873 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt
@@ -52,17 +52,15 @@
         val shortcuts = mutableListOf<KeyboardShortcutInfo>()
 
         if (keyboardA11yShortcutControl()) {
-            if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
-                shortcuts.add(
-                    // Toggle bounce keys:
-                    //  - Meta + Alt + 3
-                    shortcutInfo(
-                        resources.getString(R.string.group_accessibility_toggle_bounce_keys)
-                    ) {
-                        command(META_META_ON or META_ALT_ON, KEYCODE_3)
-                    }
-                )
-            }
+            shortcuts.add(
+                // Toggle bounce keys:
+                //  - Meta + Alt + 3
+                shortcutInfo(
+                    resources.getString(R.string.group_accessibility_toggle_bounce_keys)
+                ) {
+                    command(META_META_ON or META_ALT_ON, KEYCODE_3)
+                }
+            )
             if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
                 shortcuts.add(
                     // Toggle mouse keys:
@@ -74,28 +72,24 @@
                     }
                 )
             }
-            if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
-                shortcuts.add(
-                    // Toggle sticky keys:
-                    //  - Meta + Alt + 5
-                    shortcutInfo(
-                        resources.getString(R.string.group_accessibility_toggle_sticky_keys)
-                    ) {
-                        command(META_META_ON or META_ALT_ON, KEYCODE_5)
-                    }
-                )
-            }
-            if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
-                shortcuts.add(
-                    // Toggle slow keys:
-                    //  - Meta + Alt + 6
-                    shortcutInfo(
-                        resources.getString(R.string.group_accessibility_toggle_slow_keys)
-                    ) {
-                        command(META_META_ON or META_ALT_ON, KEYCODE_6)
-                    }
-                )
-            }
+            shortcuts.add(
+                // Toggle sticky keys:
+                //  - Meta + Alt + 5
+                shortcutInfo(
+                    resources.getString(R.string.group_accessibility_toggle_sticky_keys)
+                ) {
+                    command(META_META_ON or META_ALT_ON, KEYCODE_5)
+                }
+            )
+            shortcuts.add(
+                // Toggle slow keys:
+                //  - Meta + Alt + 6
+                shortcutInfo(
+                    resources.getString(R.string.group_accessibility_toggle_slow_keys)
+                ) {
+                    command(META_META_ON or META_ALT_ON, KEYCODE_6)
+                }
+            )
         }
 
         if (enableVoiceAccessKeyGestures()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index f85a23c..6f5f662 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,9 +21,9 @@
 import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -57,6 +57,7 @@
     keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     val deviceEntryInteractor: DeviceEntryInteractor,
@@ -116,6 +117,17 @@
         }
     }
 
+    @SuppressLint("MissingPermission")
+    private fun shouldTransitionToCommunal(
+        shouldShowCommunal: Boolean,
+        isCommunalAvailable: Boolean,
+    ) =
+        if (communalSettingsInteractor.isV2FlagEnabled()) {
+            shouldShowCommunal
+        } else {
+            isCommunalAvailable && dreamManager.canStartDreaming(false)
+        }
+
     @OptIn(FlowPreview::class)
     @SuppressLint("MissingPermission")
     private fun listenForDozingToDreaming() {
@@ -141,9 +153,9 @@
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
                     communalInteractor.isCommunalAvailable,
-                    communalSceneInteractor.isIdleOnCommunal,
+                    communalInteractor.shouldShowCommunal,
                 )
-                .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
+                .collect { (_, isCommunalAvailable, shouldShowCommunal) ->
                     val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
                     val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
                     val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
@@ -173,15 +185,9 @@
                         }
                     } else if (isKeyguardOccludedLegacy) {
                         startTransitionTo(KeyguardState.OCCLUDED)
-                    } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
-                        if (!SceneContainerFlag.isEnabled) {
-                            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
-                        }
-                    } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
-                        // Using false for isScreenOn as canStartDreaming returns false if any
-                        // dream, including doze, is active.
-                        // This case handles tapping the power button to transition through
-                        // dream -> off -> hub.
+                    } else if (
+                        shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+                    ) {
                         if (!SceneContainerFlag.isEnabled) {
                             transitionToGlanceableHub()
                         }
@@ -203,8 +209,8 @@
             powerInteractor.detailedWakefulness
                 .filterRelevantKeyguardStateAnd { it.isAwake() }
                 .sample(
+                    communalInteractor.shouldShowCommunal,
                     communalInteractor.isCommunalAvailable,
-                    communalSceneInteractor.isIdleOnCommunal,
                     keyguardInteractor.biometricUnlockState,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
                     keyguardInteractor.primaryBouncerShowing,
@@ -212,8 +218,8 @@
                 .collect {
                     (
                         _,
+                        shouldShowCommunal,
                         isCommunalAvailable,
-                        isIdleOnCommunal,
                         biometricUnlockState,
                         canWakeDirectlyToGone,
                         primaryBouncerShowing) ->
@@ -238,14 +244,9 @@
                                     ownerReason = "waking from dozing",
                                 )
                             }
-                        } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
-                            if (!SceneContainerFlag.isEnabled) {
-                                startTransitionTo(
-                                    KeyguardState.GLANCEABLE_HUB,
-                                    ownerReason = "waking from dozing",
-                                )
-                            }
-                        } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+                        } else if (
+                            shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+                        ) {
                             if (!SceneContainerFlag.isEnabled) {
                                 transitionToGlanceableHub()
                             }
@@ -260,15 +261,11 @@
         }
     }
 
-    private suspend fun transitionToGlanceableHub() {
-        if (communalSceneKtfRefactor()) {
-            communalSceneInteractor.snapToScene(
-                newScene = CommunalScenes.Communal,
-                loggingReason = "from dozing to hub",
-            )
-        } else {
-            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
-        }
+    private fun transitionToGlanceableHub() {
+        communalSceneInteractor.snapToScene(
+            newScene = CommunalScenes.Communal,
+            loggingReason = "from dozing to hub",
+        )
     }
 
     /** Dismisses keyguard from the DOZING state. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 251af11..0fb98ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -21,7 +21,6 @@
 import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -59,7 +58,6 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
-    private val glanceableHubTransitions: GlanceableHubTransitions,
     private val communalInteractor: CommunalInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
@@ -87,11 +85,7 @@
         listenForDreamingToLockscreenOrGone()
         listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
-        if (!communalSceneKtfRefactor()) {
-            listenForDreamingToGlanceableHub()
-        } else {
-            listenForDreamingToGlanceableHubFromPowerButton()
-        }
+        listenForDreamingToGlanceableHubFromPowerButton()
         listenForDreamingToPrimaryBouncer()
     }
 
@@ -105,18 +99,6 @@
         }
     }
 
-    private fun listenForDreamingToGlanceableHub() {
-        if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
-        if (SceneContainerFlag.isEnabled) return
-        scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
-            glanceableHubTransitions.listenForGlanceableHubTransition(
-                transitionOwnerName = TAG,
-                fromState = KeyguardState.DREAMING,
-                toState = KeyguardState.GLANCEABLE_HUB,
-            )
-        }
-    }
-
     /**
      * Normally when pressing power button from the dream, the devices goes from DREAMING to DOZING,
      * then [FromDozingTransitionInteractor] handles the transition to GLANCEABLE_HUB. However if
@@ -129,20 +111,37 @@
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
         if (SceneContainerFlag.isEnabled) return
         scope.launch {
-            powerInteractor.isAwake
-                .debounce(50L)
-                .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
-                .sample(communalInteractor.isCommunalAvailable)
-                .collect { isCommunalAvailable ->
-                    if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
-                        // This case handles tapping the power button to transition through
-                        // dream -> off -> hub.
-                        communalSceneInteractor.snapToScene(
-                            newScene = CommunalScenes.Communal,
-                            loggingReason = "from dreaming to hub",
-                        )
+            if (communalSettingsInteractor.isV2FlagEnabled()) {
+                powerInteractor.isAwake
+                    .debounce(50L)
+                    .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+                    .sample(communalInteractor.shouldShowCommunal)
+                    .collect { shouldShowCommunal ->
+                        if (shouldShowCommunal) {
+                            // This case handles tapping the power button to transition through
+                            // dream -> off -> hub.
+                            communalSceneInteractor.snapToScene(
+                                newScene = CommunalScenes.Communal,
+                                loggingReason = "from dreaming to hub",
+                            )
+                        }
                     }
-                }
+            } else {
+                powerInteractor.isAwake
+                    .debounce(50L)
+                    .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+                    .sample(communalInteractor.isCommunalAvailable)
+                    .collect { isCommunalAvailable ->
+                        if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+                            // This case handles tapping the power button to transition through
+                            // dream -> off -> hub.
+                            communalSceneInteractor.snapToScene(
+                                newScene = CommunalScenes.Communal,
+                                loggingReason = "from dreaming to hub",
+                            )
+                        }
+                    }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index c5d40a0..2eeba0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -31,12 +30,10 @@
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -45,10 +42,8 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.withContext
 
 @OptIn(FlowPreview::class)
 @SysUISingleton
@@ -58,7 +53,6 @@
     @Background private val scope: CoroutineScope,
     @Main mainDispatcher: CoroutineDispatcher,
     @Background bgDispatcher: CoroutineDispatcher,
-    private val glanceableHubTransitions: GlanceableHubTransitions,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     keyguardInteractor: KeyguardInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
@@ -83,9 +77,6 @@
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
             return
         }
-        if (!communalSceneKtfRefactor()) {
-            listenForHubToLockscreenOrDreaming()
-        }
         listenForHubToDozing()
         listenForHubToPrimaryBouncer()
         listenForHubToAlternateBouncer()
@@ -108,30 +99,6 @@
         }
     }
 
-    /**
-     * Listens for the glanceable hub transition to lock screen and directly drives the keyguard
-     * transition.
-     */
-    private fun listenForHubToLockscreenOrDreaming() {
-        scope.launch("$TAG#listenForGlanceableHubToLockscreenOrDream") {
-            keyguardInteractor.isDreaming.collectLatest { dreaming ->
-                withContext(mainDispatcher) {
-                    val toState =
-                        if (dreaming) {
-                            KeyguardState.DREAMING
-                        } else {
-                            KeyguardState.LOCKSCREEN
-                        }
-                    glanceableHubTransitions.listenForGlanceableHubTransition(
-                        transitionOwnerName = TAG,
-                        fromState = KeyguardState.GLANCEABLE_HUB,
-                        toState = toState,
-                    )
-                }
-            }
-        }
-    }
-
     private fun listenForHubToPrimaryBouncer() {
         scope.launch("$TAG#listenForHubToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
@@ -161,18 +128,11 @@
             powerInteractor.isAsleep
                 .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
                 .collect {
-                    if (communalSceneKtfRefactor()) {
-                        communalSceneInteractor.snapToScene(
-                            newScene = CommunalScenes.Blank,
-                            loggingReason = "hub to dozing",
-                            keyguardState = KeyguardState.DOZING,
-                        )
-                    } else {
-                        startTransitionTo(
-                            toState = KeyguardState.DOZING,
-                            modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
-                        )
-                    }
+                    communalSceneInteractor.snapToScene(
+                        newScene = CommunalScenes.Blank,
+                        loggingReason = "hub to dozing",
+                        keyguardState = KeyguardState.DOZING,
+                    )
                 }
         }
     }
@@ -202,21 +162,17 @@
                     .filterRelevantKeyguardStateAnd { onTop -> onTop }
                     .collect {
                         maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
-                            if (communalSceneKtfRefactor()) {
-                                communalSceneInteractor.changeScene(
-                                    newScene = CommunalScenes.Blank,
-                                    loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
-                                    transitionKey = CommunalTransitionKeys.SimpleFade,
-                                    keyguardState = state,
-                                )
-                                null
-                            } else {
-                                startTransitionTo(state, ownerReason = reason)
-                            }
+                            communalSceneInteractor.changeScene(
+                                newScene = CommunalScenes.Blank,
+                                loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
+                                transitionKey = CommunalTransitionKeys.SimpleFade,
+                                keyguardState = state,
+                            )
+                            null
                         }
                     }
             }
-        } else if (communalSceneKtfRefactor()) {
+        } else {
             scope.launch {
                 combine(
                         keyguardInteractor.isKeyguardOccluded,
@@ -248,56 +204,40 @@
                         )
                     }
             }
-        } else {
-            scope.launch {
-                allOf(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
-                    .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming ->
-                        isOccludedAndNotDreaming
-                    }
-                    .collect { _ -> startTransitionTo(KeyguardState.OCCLUDED) }
-            }
         }
     }
 
     private fun listenForHubToGone() {
         if (SceneContainerFlag.isEnabled) return
-        if (communalSceneKtfRefactor()) {
-            scope.launch {
-                allOf(
-                        keyguardInteractor.isKeyguardGoingAway,
-                        // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
-                        // state until after edit mode is ready to be shown.
-                        noneOf(
-                            // When launching activities from widgets on the hub, we wait to change
-                            // scenes until the activity launch is complete.
-                            communalSceneInteractor.isLaunchingWidget
-                        ),
-                    )
-                    .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
-                    .sample(communalSceneInteractor.editModeState, ::Pair)
-                    .collect { (_, editModeState) ->
-                        if (
-                            editModeState == EditModeState.STARTING ||
-                                editModeState == EditModeState.SHOWING
-                        ) {
-                            // Don't change scenes here as that is handled by the edit activity.
-                            startTransitionTo(KeyguardState.GONE)
-                        } else {
-                            communalSceneInteractor.changeScene(
-                                newScene = CommunalScenes.Blank,
-                                loggingReason = "hub to gone",
-                                transitionKey = CommunalTransitionKeys.SimpleFade,
-                                keyguardState = KeyguardState.GONE,
-                            )
-                        }
+        scope.launch {
+            allOf(
+                    keyguardInteractor.isKeyguardGoingAway,
+                    // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE
+                    // state until after edit mode is ready to be shown.
+                    noneOf(
+                        // When launching activities from widgets on the hub, we wait to change
+                        // scenes until the activity launch is complete.
+                        communalSceneInteractor.isLaunchingWidget
+                    ),
+                )
+                .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
+                .sample(communalSceneInteractor.editModeState, ::Pair)
+                .collect { (_, editModeState) ->
+                    if (
+                        editModeState == EditModeState.STARTING ||
+                            editModeState == EditModeState.SHOWING
+                    ) {
+                        // Don't change scenes here as that is handled by the edit activity.
+                        startTransitionTo(KeyguardState.GONE)
+                    } else {
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Blank,
+                            loggingReason = "hub to gone",
+                            transitionKey = CommunalTransitionKeys.SimpleFade,
+                            keyguardState = KeyguardState.GONE,
+                        )
                     }
-            }
-        } else {
-            scope.launch {
-                keyguardInteractor.isKeyguardGoingAway
-                    .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
-                    .collect { startTransitionTo(KeyguardState.GONE) }
-            }
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cf712f1..a01dc02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,7 +20,6 @@
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -69,7 +68,6 @@
     keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
     powerInteractor: PowerInteractor,
-    private val glanceableHubTransitions: GlanceableHubTransitions,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val communalInteractor: CommunalInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
@@ -96,9 +94,6 @@
         listenForLockscreenToPrimaryBouncerDragging()
         listenForLockscreenToAlternateBouncer()
         listenForLockscreenTransitionToCamera()
-        if (!communalSceneKtfRefactor()) {
-            listenForLockscreenToGlanceableHub()
-        }
         if (communalSettingsInteractor.isV2FlagEnabled()) {
             listenForLockscreenToGlanceableHubV2()
         }
@@ -358,24 +353,6 @@
         }
     }
 
-    /**
-     * Listens for transition from glanceable hub back to lock screen and directly drives the
-     * keyguard transition.
-     */
-    private fun listenForLockscreenToGlanceableHub() {
-        if (SceneContainerFlag.isEnabled) return
-        if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
-            return
-        }
-        scope.launch(context = mainDispatcher) {
-            glanceableHubTransitions.listenForGlanceableHubTransition(
-                transitionOwnerName = TAG,
-                fromState = KeyguardState.LOCKSCREEN,
-                toState = KeyguardState.GLANCEABLE_HUB,
-            )
-        }
-    }
-
     private fun listenForLockscreenToGlanceableHubV2() {
         scope.launch {
             communalInteractor.shouldShowCommunal
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 284298d..a2cfdc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.Flags.restartDreamOnUnocclude
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -143,15 +142,11 @@
             startTransitionTo(KeyguardState.DREAMING)
         } else if (isIdleOnCommunal || showCommunalFromOccluded) {
             if (SceneContainerFlag.isEnabled) return
-            if (communalSceneKtfRefactor()) {
-                communalSceneInteractor.changeScene(
-                    newScene = CommunalScenes.Communal,
-                    loggingReason = "occluded to hub",
-                    transitionKey = CommunalTransitionKeys.SimpleFade,
-                )
-            } else {
-                startTransitionTo(KeyguardState.GLANCEABLE_HUB)
-            }
+            communalSceneInteractor.changeScene(
+                newScene = CommunalScenes.Communal,
+                loggingReason = "occluded to hub",
+                transitionKey = CommunalTransitionKeys.SimpleFade,
+            )
         } else {
             startTransitionTo(KeyguardState.LOCKSCREEN)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 402c152..30c1aac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,8 +18,8 @@
 
 import android.animation.ValueAnimator
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +35,6 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.shared.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -45,7 +44,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class FromPrimaryBouncerTransitionInteractor
@@ -173,8 +171,7 @@
         // If the hub is showing, and we are not animating a widget launch nor transitioning to
         // edit mode, then close the hub immediately.
         if (
-            communalSceneKtfRefactor() &&
-                communalSceneInteractor.isIdleOnCommunal.value &&
+            communalSceneInteractor.isIdleOnCommunal.value &&
                 !communalSceneInteractor.isLaunchingWidget.value &&
                 communalSceneInteractor.editModeState.value == null
         ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
deleted file mode 100644
index bde0f56..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-
-class GlanceableHubTransitions
-@Inject
-constructor(
-    private val transitionInteractor: KeyguardTransitionInteractor,
-    private val transitionRepository: KeyguardTransitionRepository,
-    private val communalInteractor: CommunalInteractor,
-) {
-    /**
-     * Listens for the glanceable hub transition to the specified scene and directly drives the
-     * keyguard transition between the lockscreen and the hub.
-     *
-     * The glanceable hub transition progress is used as the source of truth as it cannot be driven
-     * externally. The progress is used for both transitions caused by user touch input or by
-     * programmatic changes.
-     */
-    suspend fun listenForGlanceableHubTransition(
-        transitionOwnerName: String,
-        fromState: KeyguardState,
-        toState: KeyguardState,
-    ) {
-        if (SceneContainerFlag.isEnabled) return
-        val toScene =
-            if (fromState == KeyguardState.GLANCEABLE_HUB) {
-                CommunalScenes.Blank
-            } else {
-                CommunalScenes.Communal
-            }
-        var transitionId: UUID? = null
-
-        communalInteractor
-            .transitionProgressToScene(toScene)
-            .sample(
-                transitionInteractor.startedKeyguardTransitionStep,
-                ::Pair,
-            )
-            .collect { (transitionProgress, lastStartedStep) ->
-                val id = transitionId
-                if (id == null) {
-                    // No transition started.
-                    if (
-                        transitionProgress is CommunalTransitionProgressModel.Transition &&
-                            lastStartedStep.to == fromState
-                    ) {
-                        transitionId =
-                            transitionRepository.startTransition(
-                                TransitionInfo(
-                                    ownerName = transitionOwnerName,
-                                    from = fromState,
-                                    to = toState,
-                                    animator = null, // transition will be manually controlled
-                                )
-                            )
-                    }
-                } else {
-                    if (lastStartedStep.to != toState) {
-                        return@collect
-                    }
-                    // An existing `id` means a transition is started, and calls to
-                    // `updateTransition` will control it until FINISHED or CANCELED
-                    val nextState: TransitionState
-                    val progressFraction: Float
-                    when (transitionProgress) {
-                        is CommunalTransitionProgressModel.Idle -> {
-                            if (transitionProgress.scene == toScene) {
-                                nextState = TransitionState.FINISHED
-                                progressFraction = 1f
-                            } else {
-                                nextState = TransitionState.CANCELED
-                                progressFraction = 0f
-                            }
-                        }
-                        is CommunalTransitionProgressModel.Transition -> {
-                            nextState = TransitionState.RUNNING
-                            progressFraction = transitionProgress.progress
-                        }
-                        is CommunalTransitionProgressModel.OtherTransition -> {
-                            // Shouldn't happen but if another transition starts during the
-                            // current one, mark the current one as canceled.
-                            nextState = TransitionState.CANCELED
-                            progressFraction = 0f
-                        }
-                    }
-                    transitionRepository.updateTransition(
-                        id,
-                        progressFraction,
-                        nextState,
-                    )
-
-                    if (
-                        nextState == TransitionState.CANCELED ||
-                            nextState == TransitionState.FINISHED
-                    ) {
-                        transitionId = null
-                    }
-
-                    // If canceled, just put the state back.
-                    if (nextState == TransitionState.CANCELED) {
-                        transitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = transitionOwnerName,
-                                from = toState,
-                                to = fromState,
-                                animator =
-                                    ValueAnimator().apply {
-                                        interpolator = Interpolators.LINEAR
-                                        duration = 0
-                                    }
-                            )
-                        )
-                    }
-                }
-            }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 8f68158..c5127a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -66,11 +66,14 @@
          * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
          * valid.
          *
+         * Note that [onStep] accepts a null return value. When null, no animation information will
+         * be emitted, effectively saying "do not change the value on this frame"
+         *
          * Note that [onCancel] isn't used when the scene framework is enabled.
          */
         fun sharedFlow(
             duration: Duration = transitionDuration,
-            onStep: (Float) -> Float,
+            onStep: (Float) -> Float?,
             startTime: Duration = 0.milliseconds,
             onStart: (() -> Unit)? = null,
             onCancel: (() -> Float)? = null,
@@ -102,7 +105,7 @@
          */
         fun sharedFlowWithState(
             duration: Duration,
-            onStep: (Float) -> Float,
+            onStep: (Float) -> Float?,
             startTime: Duration = 0.milliseconds,
             onStart: (() -> Unit)? = null,
             onCancel: (() -> Float)? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index dc7fefa..65c0a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -54,7 +54,12 @@
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
-        transitionAnimation.immediatelyTransitionTo(0f)
+        transitionAnimation.sharedFlow(
+            duration = TO_DOZING_DURATION,
+            onStep = { null },
+            onFinish = { 0f },
+            onCancel = { 0f },
+        )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index d63c2e0..0107a52 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -23,11 +23,11 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewOutlineProvider
+import androidx.annotation.VisibleForTesting
 import androidx.core.view.GestureDetectorCompat
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.app.tracing.TraceStateLogger
-import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
@@ -38,9 +38,10 @@
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.shared.animation.PhysicsAnimator
+import kotlin.math.sign
 
 private const val FLING_SLOP = 1000000
-private const val DISMISS_DELAY = 100L
+@VisibleForTesting const val DISMISS_DELAY = 100L
 private const val SCROLL_DELAY = 100L
 private const val RUBBERBAND_FACTOR = 0.2f
 private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
@@ -64,7 +65,7 @@
     private val closeGuts: (immediate: Boolean) -> Unit,
     private val falsingManager: FalsingManager,
     private val logSmartspaceImpression: (Boolean) -> Unit,
-    private val logger: MediaUiEventLogger
+    private val logger: MediaUiEventLogger,
 ) {
     /** Trace state logger for media carousel visibility */
     private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
@@ -96,7 +97,7 @@
 
     /** What's the currently visible player index? */
     var visibleMediaIndex: Int = 0
-        private set
+        @VisibleForTesting set
 
     /** How much are we scrolled into the current media? */
     private var scrollIntoCurrentMedia: Int = 0
@@ -137,14 +138,14 @@
                 eStart: MotionEvent?,
                 eCurrent: MotionEvent,
                 vX: Float,
-                vY: Float
+                vY: Float,
             ) = onFling(vX, vY)
 
             override fun onScroll(
                 down: MotionEvent?,
                 lastMotion: MotionEvent,
                 distanceX: Float,
-                distanceY: Float
+                distanceY: Float,
             ) = onScroll(down!!, lastMotion, distanceX)
 
             override fun onDown(e: MotionEvent): Boolean {
@@ -157,6 +158,7 @@
     val touchListener =
         object : Gefingerpoken {
             override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+
             override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
         }
 
@@ -168,7 +170,7 @@
                 scrollX: Int,
                 scrollY: Int,
                 oldScrollX: Int,
-                oldScrollY: Int
+                oldScrollY: Int,
             ) {
                 if (playerWidthPlusPadding == 0) {
                     return
@@ -177,7 +179,7 @@
                 val relativeScrollX = scrollView.relativeScrollX
                 onMediaScrollingChanged(
                     relativeScrollX / playerWidthPlusPadding,
-                    relativeScrollX % playerWidthPlusPadding
+                    relativeScrollX % playerWidthPlusPadding,
                 )
             }
         }
@@ -209,7 +211,7 @@
                         0,
                         carouselWidth,
                         carouselHeight,
-                        cornerRadius.toFloat()
+                        cornerRadius.toFloat(),
                     )
                 }
             }
@@ -235,7 +237,7 @@
                     getMaxTranslation().toFloat(),
                     0.0f,
                     1.0f,
-                    Math.abs(contentTranslation)
+                    Math.abs(contentTranslation),
                 )
             val settingsTranslation =
                 (1.0f - settingsOffset) *
@@ -323,7 +325,7 @@
                         CONTENT_TRANSLATION,
                         newTranslation,
                         startVelocity = 0.0f,
-                        config = translationConfig
+                        config = translationConfig,
                     )
                     .start()
                 scrollView.animationTargetX = newTranslation
@@ -391,7 +393,7 @@
                         CONTENT_TRANSLATION,
                         newTranslation,
                         startVelocity = 0.0f,
-                        config = translationConfig
+                        config = translationConfig,
                     )
                     .start()
             } else {
@@ -430,7 +432,7 @@
                     CONTENT_TRANSLATION,
                     newTranslation,
                     startVelocity = vX,
-                    config = translationConfig
+                    config = translationConfig,
                 )
                 .start()
             scrollView.animationTargetX = newTranslation
@@ -583,10 +585,35 @@
         // We need to post this to wait for the active player becomes visible.
         mainExecutor.executeDelayed(
             { scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
-            SCROLL_DELAY
+            SCROLL_DELAY,
         )
     }
 
+    /**
+     * Scrolls the media carousel by the number of players specified by [step]. If scrolling beyond
+     * the carousel's bounds:
+     * - If the carousel is not dismissible, the settings button is displayed.
+     * - If the carousel is dismissible, no action taken.
+     *
+     * @param step A positive number means next, and negative means previous.
+     */
+    fun scrollByStep(step: Int) {
+        val destIndex = visibleMediaIndex + step
+        if (destIndex >= mediaContent.childCount || destIndex < 0) {
+            if (!showsSettingsButton) return
+            var translation = getMaxTranslation() * sign(-step.toFloat())
+            translation = if (isRtl) -translation else translation
+            PhysicsAnimator.getInstance(this)
+                .spring(CONTENT_TRANSLATION, translation, config = translationConfig)
+                .start()
+            scrollView.animationTargetX = translation
+        } else if (scrollView.getContentTranslation() != 0.0f) {
+            resetTranslation(true)
+        } else {
+            scrollToPlayer(destIndex = destIndex)
+        }
+    }
+
     companion object {
         private val CONTENT_TRANSLATION =
             object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index be814ae..3d2aafe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -440,6 +440,7 @@
             updateEndAreaColor(groupStatus.selected() ? mController.getColorSeekbarProgress()
                     : mController.getColorItemBackground());
             mEndTouchArea.setContentDescription(getDeviceItemContentDescription(device));
+            mCheckBox.setOnCheckedChangeListener(null);
             mCheckBox.setChecked(groupStatus.selected());
             mCheckBox.setOnCheckedChangeListener(
                     isEnabled ? (buttonView, isChecked) -> onGroupActionTriggered(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 16dff7d..11b014c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.log.core.Logger
 import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -83,34 +84,78 @@
             .flowOn(backgroundDispatcher)
 
     /** Sets for the current user the set of [TileSpec] to display as large tiles. */
-    fun setLargeTilesSpecs(specs: Set<TileSpec>) {
-        setLargeTilesSpecsForUser(specs, userRepository.getSelectedUserInfo().id)
-    }
-
-    private fun setLargeTilesSpecsForUser(specs: Set<TileSpec>, userId: Int) {
-        with(getSharedPrefs(userId)) {
-            edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+    fun writeLargeTileSpecs(specs: Set<TileSpec>) {
+        with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
+            writeLargeTileSpecs(specs)
+            setLargeTilesDefault(false)
         }
     }
 
+    suspend fun deleteLargeTileDataJob() {
+        userRepository.selectedUserInfo.collect { userInfo ->
+            getSharedPrefs(userInfo.id)
+                .edit()
+                .remove(LARGE_TILES_SPECS_KEY)
+                .remove(LARGE_TILES_DEFAULT_KEY)
+                .apply()
+        }
+    }
+
+    private fun SharedPreferences.writeLargeTileSpecs(specs: Set<TileSpec>) {
+        edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+    }
+
     /**
-     * Sets the initial tiles as large, if there is no set in SharedPrefs for the [userId]. This is
-     * to be used when upgrading to a build that supports large/small tiles.
+     * Sets the initial set of large tiles. One of the following cases will happen:
+     * * If we are setting the default set (no value stored in settings for the list of tiles), set
+     *   the large tiles based on [defaultLargeTilesRepository]. We do this to signal future reboots
+     *   that we have performed the upgrade path once. In this case, we will mark that we set them
+     *   as the default in case a restore needs to modify them later.
+     * * If we got a list of tiles restored from a device and nothing has modified the list of
+     *   tiles, set all the restored tiles to large. Note that if we also restored a set of large
+     *   tiles before this was called, [LARGE_TILES_DEFAULT_KEY] will be false and we won't
+     *   overwrite it.
+     * * If we got a list of tiles from settings, we consider that we upgraded in place and then we
+     *   will set all those tiles to large IF there's no current set of large tiles.
      *
      * Even if largeTilesSpec is read Eagerly before we know if we are in an initial state, because
      * we are not writing the default values to the SharedPreferences, the file will not contain the
      * key and this call will succeed, as long as there hasn't been any calls to setLargeTilesSpecs
      * for that user before.
      */
-    fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, userId: Int) {
+    fun setInitialOrUpgradeLargeTiles(upgradePath: TilesUpgradePath, userId: Int) {
         with(getSharedPrefs(userId)) {
-            if (!contains(LARGE_TILES_SPECS_KEY)) {
-                logger.i("Setting upgraded large tiles for user $userId: $specs")
-                setLargeTilesSpecsForUser(specs, userId)
+            when (upgradePath) {
+                is TilesUpgradePath.DefaultSet -> {
+                    writeLargeTileSpecs(defaultLargeTilesRepository.defaultLargeTiles)
+                    logger.i("Large tiles set to default on init")
+                    setLargeTilesDefault(true)
+                }
+                is TilesUpgradePath.RestoreFromBackup -> {
+                    if (
+                        getBoolean(LARGE_TILES_DEFAULT_KEY, false) ||
+                            !contains(LARGE_TILES_SPECS_KEY)
+                    ) {
+                        writeLargeTileSpecs(upgradePath.value)
+                        logger.i("Tiles restored from backup set to large: ${upgradePath.value}")
+                        setLargeTilesDefault(false)
+                    }
+                }
+                is TilesUpgradePath.ReadFromSettings -> {
+                    if (!contains(LARGE_TILES_SPECS_KEY)) {
+                        writeLargeTileSpecs(upgradePath.value)
+                        logger.i("Tiles read from settings set to large: ${upgradePath.value}")
+                        setLargeTilesDefault(false)
+                    }
+                }
             }
         }
     }
 
+    private fun SharedPreferences.setLargeTilesDefault(value: Boolean) {
+        edit().putBoolean(LARGE_TILES_DEFAULT_KEY, value).apply()
+    }
+
     private fun getSharedPrefs(userId: Int): SharedPreferences {
         return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
     }
@@ -118,6 +163,7 @@
     companion object {
         private const val TAG = "QSPreferencesRepository"
         private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+        private const val LARGE_TILES_DEFAULT_KEY = "large_tiles_default"
         const val FILE_NAME = "quick_settings_prefs"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
index 86838b4..9b98797 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -27,10 +28,20 @@
     val largeTilesSpecs: Flow<Set<TileSpec>> = repo.largeTilesSpecs
 
     fun setLargeTilesSpecs(specs: Set<TileSpec>) {
-        repo.setLargeTilesSpecs(specs)
+        repo.writeLargeTileSpecs(specs)
     }
 
-    fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, user: Int) {
-        repo.setInitialLargeTilesSpecs(specs, user)
+    /**
+     * This method should be called to indicate that a "new" set of tiles has been determined for a
+     * particular user coming from different upgrade sources.
+     *
+     * @see TilesUpgradePath for more information
+     */
+    fun setInitialOrUpgradeLargeTilesSpecs(specs: TilesUpgradePath, user: Int) {
+        repo.setInitialOrUpgradeLargeTiles(specs, user)
+    }
+
+    suspend fun deleteLargeTilesDataJob() {
+        repo.deleteLargeTileDataJob()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
index a8ac5c3..e279735 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
@@ -19,11 +19,13 @@
 import com.android.app.tracing.coroutines.launchTraced
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.flags.QsInCompose
 import com.android.systemui.qs.panels.domain.interactor.QSPreferencesInteractor
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
 
 class QSPanelsCoreStartable
 @Inject
@@ -33,10 +35,14 @@
     @Background private val backgroundApplicationScope: CoroutineScope,
 ) : CoreStartable {
     override fun start() {
-        backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
-            tileSpecRepository.tilesReadFromSetting.receiveAsFlow().collect { (tiles, userId) ->
-                preferenceInteractor.setInitialLargeTilesSpecs(tiles, userId)
+        if (QsInCompose.isEnabled) {
+            backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
+                tileSpecRepository.tilesUpgradePath.receiveAsFlow().collect { (tiles, userId) ->
+                    preferenceInteractor.setInitialOrUpgradeLargeTilesSpecs(tiles, userId)
+                }
             }
+        } else {
+            backgroundApplicationScope.launch { preferenceInteractor.deleteLargeTilesDataJob() }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 6b7dd38..c50d5da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.RetailModeRepository
@@ -78,7 +79,7 @@
     /** Reset the current set of tiles to the default list of tiles */
     suspend fun resetToDefault(userId: Int)
 
-    val tilesReadFromSetting: ReceiveChannel<Pair<Set<TileSpec>, Int>>
+    val tilesUpgradePath: ReceiveChannel<Pair<TilesUpgradePath, Int>>
 
     companion object {
         /** Position to indicate the end of the list */
@@ -112,8 +113,8 @@
             .filter { it !is TileSpec.Invalid }
     }
 
-    private val _tilesReadFromSetting = Channel<Pair<Set<TileSpec>, Int>>(capacity = 5)
-    override val tilesReadFromSetting = _tilesReadFromSetting
+    private val _tilesUpgradePath = Channel<Pair<TilesUpgradePath, Int>>(capacity = 5)
+    override val tilesUpgradePath = _tilesUpgradePath
 
     private val userTileRepositories = SparseArray<UserTileSpecRepository>()
 
@@ -122,8 +123,8 @@
             val userTileRepository = userTileSpecRepositoryFactory.create(userId)
             userTileRepositories.put(userId, userTileRepository)
             applicationScope.launchTraced("TileSpecRepository.aggregateTilesPerUser") {
-                for (tilesFromSettings in userTileRepository.tilesReadFromSettings) {
-                    _tilesReadFromSetting.send(tilesFromSettings to userId)
+                for (tileUpgrade in userTileRepository.tilesUpgradePath) {
+                    _tilesUpgradePath.send(tileUpgrade to userId)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 7b56cd9..5aa5eda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.util.settings.SecureSettings
 import dagger.assisted.Assisted
@@ -49,8 +50,8 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
-    private val _tilesReadFromSettings = Channel<Set<TileSpec>>(capacity = 2)
-    val tilesReadFromSettings: ReceiveChannel<Set<TileSpec>> = _tilesReadFromSettings
+    private val _tilesUpgradePath = Channel<TilesUpgradePath>(capacity = 3)
+    val tilesUpgradePath: ReceiveChannel<TilesUpgradePath> = _tilesUpgradePath
 
     private val defaultTiles: List<TileSpec>
         get() = defaultTilesRepository.defaultTiles
@@ -67,14 +68,23 @@
                     .scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
                         change
                             .apply(current)
-                            .also {
-                                if (current != it) {
+                            .also { afterRestore ->
+                                if (current != afterRestore) {
                                     if (change is RestoreTiles) {
-                                        logger.logTilesRestoredAndReconciled(current, it, userId)
+                                        logger.logTilesRestoredAndReconciled(
+                                            current,
+                                            afterRestore,
+                                            userId,
+                                        )
                                     } else {
-                                        logger.logProcessTileChange(change, it, userId)
+                                        logger.logProcessTileChange(change, afterRestore, userId)
                                     }
                                 }
+                                if (change is RestoreTiles) {
+                                    _tilesUpgradePath.send(
+                                        TilesUpgradePath.RestoreFromBackup(afterRestore.toSet())
+                                    )
+                                }
                             }
                             // Distinct preserves the order of the elements removing later
                             // duplicates,
@@ -154,7 +164,9 @@
     private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> {
         val loadedTiles = loadTilesFromSettings(userId)
         if (loadedTiles.isNotEmpty()) {
-            _tilesReadFromSettings.send(loadedTiles.toSet())
+            _tilesUpgradePath.send(TilesUpgradePath.ReadFromSettings(loadedTiles.toSet()))
+        } else {
+            _tilesUpgradePath.send(TilesUpgradePath.DefaultSet)
         }
         return parseTileSpecs(loadedTiles, userId)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
new file mode 100644
index 0000000..98f30c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 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.pipeline.shared
+
+/** Upgrade paths indicating the source of the list of QS tiles. */
+sealed interface TilesUpgradePath {
+
+    sealed interface UpgradeWithTiles : TilesUpgradePath {
+        val value: Set<TileSpec>
+    }
+
+    /** This indicates a set of tiles that was read from Settings on user start */
+    @JvmInline value class ReadFromSettings(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+    /** This indicates a set of tiles that was restored from backup */
+    @JvmInline value class RestoreFromBackup(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+    /**
+     * This indicates that no tiles were read from Settings on user start so the default has been
+     * stored.
+     */
+    data object DefaultSet : TilesUpgradePath
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 01bcc24..efdf5be 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
@@ -218,39 +219,36 @@
      * it being a false touch.
      */
     fun canChangeScene(toScene: SceneKey): Boolean {
-        val interactionTypeOrNull =
-            when (toScene) {
-                Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
-                Scenes.Gone -> Classifier.UNLOCK
-                Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
-                Scenes.QuickSettings -> Classifier.QUICK_SETTINGS
-                else -> null
-            }
-
-        val fromScene = currentScene.value
-        val isAllowed =
-            interactionTypeOrNull?.let { interactionType ->
-                // It's important that the falsing system is always queried, even if no enforcement
-                // will occur. This helps build up the right signal in the system.
-                val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
-
-                // Only enforce falsing if moving from the lockscreen scene to a new scene.
-                val fromLockscreenScene = fromScene == Scenes.Lockscreen
-
-                !fromLockscreenScene || !isFalseTouch
-            } ?: true
-
-        if (isAllowed) {
+        return isInteractionAllowedByFalsing(toScene).also {
             // A scene change is guaranteed; log it.
             logger.logSceneChanged(
-                from = fromScene,
+                from = currentScene.value,
                 to = toScene,
                 sceneState = null,
                 reason = "user interaction",
                 isInstant = false,
             )
         }
-        return isAllowed
+    }
+
+    /**
+     * Returns `true` if showing the [newlyShown] overlay is currently allowed; `false` otherwise.
+     *
+     * This is invoked only for user-initiated transitions. The goal is to check with the falsing
+     * system whether the overlay change should be rejected due to it being a false touch.
+     */
+    fun canShowOrReplaceOverlay(
+        newlyShown: OverlayKey,
+        beingReplaced: OverlayKey? = null,
+    ): Boolean {
+        return isInteractionAllowedByFalsing(newlyShown).also {
+            // An overlay change is guaranteed; log it.
+            logger.logOverlayChangeRequested(
+                from = beingReplaced,
+                to = newlyShown,
+                reason = "user interaction",
+            )
+        }
     }
 
     /**
@@ -313,6 +311,34 @@
         return sceneInteractor.filteredUserActions(unfiltered)
     }
 
+    /**
+     * Returns `true` if transitioning to [content] is permissible by the falsing system; `false`
+     * otherwise.
+     */
+    private fun isInteractionAllowedByFalsing(content: ContentKey): Boolean {
+        val interactionTypeOrNull =
+            when (content) {
+                Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
+                Scenes.Gone -> Classifier.UNLOCK
+                Scenes.Shade,
+                Overlays.NotificationsShade -> Classifier.NOTIFICATION_DRAG_DOWN
+                Scenes.QuickSettings,
+                Overlays.QuickSettingsShade -> Classifier.QUICK_SETTINGS
+                else -> null
+            }
+
+        return interactionTypeOrNull?.let { interactionType ->
+            // It's important that the falsing system is always queried, even if no enforcement
+            // will occur. This helps build up the right signal in the system.
+            val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+
+            // Only enforce falsing if moving from the lockscreen scene to new content.
+            val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+
+            !fromLockscreenScene || !isFalseTouch
+        } ?: true
+    }
+
     /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
     interface MotionEventHandler {
         /** Notifies that a [MotionEvent] has occurred. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index a379ef7..305e71e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -520,7 +520,10 @@
         val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled()
         if (
             !hubShowing &&
-                (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
+                (touchOnNotifications ||
+                    touchOnUmo ||
+                    touchOnSmartspace ||
+                    !communalViewModel.swipeToHubEnabled())
         ) {
             logger.d({
                 "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 9a79e1a..ce48c85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -31,16 +31,26 @@
 import android.provider.AlarmClock
 import android.view.DisplayCutout
 import android.view.View
+import android.view.ViewGroup
 import android.view.WindowInsets
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.dp
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.core.view.doOnLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterView.MODE_ESTIMATE
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.demomode.DemoMode
@@ -60,12 +70,15 @@
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.core.NewStatusBarIcons
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryWithEstimate
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -76,6 +89,7 @@
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Named
+import kotlinx.coroutines.flow.MutableStateFlow
 
 /**
  * Controller for QS header.
@@ -100,6 +114,7 @@
     private val shadeDisplaysRepositoryLazy: Lazy<ShadeDisplaysRepository>,
     private val variableDateViewControllerFactory: VariableDateViewController.Factory,
     @Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
+    private val batteryViewModelFactory: BatteryViewModel.Factory,
     private val dumpManager: DumpManager,
     private val shadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder,
     private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
@@ -162,6 +177,8 @@
     private var lastInsets: WindowInsets? = null
     private var nextAlarmIntent: PendingIntent? = null
 
+    private val showBatteryEstimate = MutableStateFlow(false)
+
     private var qsDisabled = false
     private var visible = false
         set(value) {
@@ -323,10 +340,6 @@
 
     override fun onInit() {
         variableDateViewControllerFactory.create(date as VariableDateView).init()
-        batteryMeterViewController.init()
-
-        // battery settings same as in QS icons
-        batteryMeterViewController.ignoreTunerUpdates()
 
         val fgColor =
             Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
@@ -336,11 +349,36 @@
         iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
         iconManager.setTint(fgColor, bgColor)
 
-        batteryIcon.updateColors(
-            fgColor /* foreground */,
-            bgColor /* background */,
-            fgColor, /* single tone (current default) */
-        )
+        if (!NewStatusBarIcons.isEnabled) {
+            batteryMeterViewController.init()
+
+            // battery settings same as in QS icons
+            batteryMeterViewController.ignoreTunerUpdates()
+
+            batteryIcon.isVisible = true
+            batteryIcon.updateColors(
+                fgColor /* foreground */,
+                bgColor /* background */,
+                fgColor, /* single tone (current default) */
+            )
+        } else {
+            // Configure the compose battery view
+            val batteryComposeView =
+                ComposeView(mView.context).apply {
+                    setContent {
+                        val showBatteryEstimate by showBatteryEstimate.collectAsStateWithLifecycle()
+                        BatteryWithEstimate(
+                            modifier = Modifier.height(17.dp).wrapContentWidth(),
+                            viewModelFactory = batteryViewModelFactory,
+                            isDark = { true },
+                            showEstimate = showBatteryEstimate,
+                        )
+                    }
+                }
+            mView.requireViewById<ViewGroup>(R.id.hover_system_icons_container).apply {
+                addView(batteryComposeView, -1)
+            }
+        }
 
         carrierIconSlots =
             listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
@@ -474,7 +512,11 @@
 
     private fun updateBatteryMode() {
         qsBatteryModeController.getBatteryMode(cutout, qsExpandedFraction)?.let {
-            batteryIcon.setPercentShowMode(it)
+            if (NewStatusBarIcons.isEnabled) {
+                showBatteryEstimate.value = it == MODE_ESTIMATE
+            } else {
+                batteryIcon.setPercentShowMode(it)
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index b9df9f86..7d4b0ed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -45,13 +45,17 @@
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeModeInteractorImpl
+import com.android.systemui.window.dagger.WindowRootViewBlurModule
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import javax.inject.Provider
 
 /** Module for classes related to the notification shade. */
-@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
+@Module(
+    includes =
+        [StartShadeModule::class, ShadeViewProviderModule::class, WindowRootViewBlurModule::class]
+)
 abstract class ShadeModule {
     companion object {
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index 8f4e870..1ab0b93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.scene.domain.SceneFrameworkTableLog
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
@@ -32,6 +33,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -89,10 +91,14 @@
 ) : ShadeModeInteractor {
 
     private val isDualShadeEnabled: Flow<Boolean> =
-        secureSettingsRepository.boolSetting(
-            Settings.Secure.DUAL_SHADE,
-            defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
-        )
+        if (SceneContainerFlag.isEnabled) {
+            secureSettingsRepository.boolSetting(
+                Settings.Secure.DUAL_SHADE,
+                defaultValue = DUAL_SHADE_ENABLED_DEFAULT,
+            )
+        } else {
+            flowOf(false)
+        }
 
     override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 25ebc8c..f06565f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,8 +24,10 @@
 import static android.os.Flags.allowPrivateProfile;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
@@ -44,6 +46,7 @@
 import android.database.ExecutorContentObserver;
 import android.net.Uri;
 import android.os.Looper;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -118,6 +121,11 @@
             Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
     private static final Uri SHOW_PRIVATE_LOCKSCREEN =
             Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+    private static final Uri REDACT_OTP_ON_WIFI =
+            Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI);
+
+    private static final Uri REDACT_OTP_IMMEDIATELY =
+            Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY);
 
     private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
             TimeUnit.MINUTES.toMillis(10);
@@ -307,6 +315,9 @@
     @VisibleForTesting
     protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false);
 
+    protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true);
+    protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false);
+
     protected int mCurrentUserId = 0;
 
     protected NotificationPresenter mPresenter;
@@ -363,6 +374,8 @@
 
         mLockScreenUris.add(SHOW_LOCKSCREEN);
         mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
+        mLockScreenUris.add(REDACT_OTP_ON_WIFI);
+        mLockScreenUris.add(REDACT_OTP_IMMEDIATELY);
 
         dumpManager.registerDumpable(this);
 
@@ -432,6 +445,10 @@
                         changed |= updateUserShowSettings(user.getIdentifier());
                     } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
                         changed |= updateUserShowPrivateSettings(user.getIdentifier());
+                    } else if (REDACT_OTP_ON_WIFI.equals(uri)) {
+                        changed |= updateRedactOtpOnWifiSetting();
+                    } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) {
+                        changed |= updateRedactOtpImmediatelySetting();
                     }
                 }
 
@@ -465,6 +482,14 @@
                 true,
                 mLockscreenSettingsObserver,
                 USER_ALL);
+        mSecureSettings.registerContentObserverAsync(
+                REDACT_OTP_ON_WIFI,
+                mLockscreenSettingsObserver
+        );
+        mSecureSettings.registerContentObserverAsync(
+                REDACT_OTP_IMMEDIATELY,
+                mLockscreenSettingsObserver
+        );
 
 
         mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
@@ -602,6 +627,28 @@
     }
 
     @WorkerThread
+    private boolean updateRedactOtpOnWifiSetting() {
+        boolean originalValue = mRedactOtpOnWifi.get();
+        boolean newValue = mSecureSettings.getIntForUser(
+                REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
+                0,
+                Process.myUserHandle().getIdentifier()) != 0;
+        mRedactOtpOnWifi.set(newValue);
+        return originalValue != newValue;
+    }
+
+    @WorkerThread
+    private boolean updateRedactOtpImmediatelySetting() {
+        boolean originalValue = mRedactOtpImmediately.get();
+        boolean newValue = mSecureSettings.getIntForUser(
+                REDACT_OTP_NOTIFICATION_IMMEDIATELY,
+                0,
+                Process.myUserHandle().getIdentifier()) != 0;
+        mRedactOtpImmediately.set(newValue);
+        return originalValue != newValue;
+    }
+
+    @WorkerThread
     private boolean updateGlobalKeyguardSettings() {
         final boolean oldValue = mKeyguardAllowingNotifications;
         mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
@@ -769,23 +816,31 @@
             return false;
         }
 
-        if (mConnectedToWifi.get()) {
-            return false;
+        if (!mRedactOtpOnWifi.get()) {
+            if (mConnectedToWifi.get()) {
+                return false;
+            }
+
+            long lastWifiConnectTime = mLastWifiConnectionTime.get();
+            // If the device has connected to wifi since receiving the notification, do not redact
+            if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
+                return false;
+            }
         }
 
         if (ent.getRanking() == null || !ent.getRanking().hasSensitiveContent()) {
             return false;
         }
 
-        long lastWifiConnectTime = mLastWifiConnectionTime.get();
-        // If the device has connected to wifi since receiving the notification, do not redact
-        if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
-            return false;
+        long latestTimeForRedaction;
+        if (mRedactOtpImmediately.get()) {
+            latestTimeForRedaction = mLastLockTime.get();
+        } else {
+            // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
+            // when this notification arrived, do not redact
+            latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
         }
 
-        // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS when
-        // this notification arrived, do not redact
-        long latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
         if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index a2c0226..f466278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -32,9 +32,7 @@
 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
-import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
@@ -86,12 +84,7 @@
                                 OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
                             }
 
-                        val colors =
-                            if (StatusBarNotifChips.isEnabled && state.promotedContent != null) {
-                                state.promotedContent.toCustomColorsModel()
-                            } else {
-                                ColorsModel.Themed
-                            }
+                        val colors = ColorsModel.AccentThemed
 
                         // This block mimics OngoingCallController#updateChip.
                         if (state.startTimeMs <= 0L) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 8357df4..2d6102e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -85,8 +85,7 @@
                     contentDescription,
                 )
             }
-        val colors = this.promotedContent.toCustomColorsModel()
-
+        val colors = ColorsModel.SystemThemed
         val clickListener: () -> Unit = {
             // The notification pipeline needs everything to run on the main thread, so keep
             // this event on the main thread.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 456cd12..d41353b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.ui.binder
 
 import android.annotation.IdRes
+import android.content.Context
 import android.content.res.ColorStateList
 import android.graphics.drawable.GradientDrawable
 import android.view.View
@@ -32,6 +33,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
@@ -76,8 +78,10 @@
                 chipTimeView.setTextColor(textColor)
                 chipTextView.setTextColor(textColor)
                 chipShortTimeDeltaView.setTextColor(textColor)
-                (chipBackgroundView.background as GradientDrawable).color =
-                    chipModel.colors.background(chipContext)
+                (chipBackgroundView.background as GradientDrawable).setBackgroundColors(
+                    chipModel.colors,
+                    chipContext,
+                )
             }
             is OngoingActivityChipModel.Inactive -> {
                 // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
@@ -460,5 +464,20 @@
         chipView.minimumWidth = minimumWidth
     }
 
+    private fun GradientDrawable.setBackgroundColors(colors: ColorsModel, context: Context) {
+        this.color = colors.background(context)
+        val outline = colors.outline(context)
+        if (outline != null) {
+            this.setStroke(
+                context.resources.getDimensionPixelSize(
+                    R.dimen.ongoing_activity_chip_outline_width
+                ),
+                outline,
+            )
+        } else {
+            this.setStroke(0, /* color= */ 0)
+        }
+    }
+
     @IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 32de0fb..8443d10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -20,16 +20,9 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
@@ -37,6 +30,8 @@
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.TextMeasurer
+import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.rememberTextMeasurer
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
@@ -83,15 +78,14 @@
                 softWrap = false,
                 modifier =
                     modifier
-                        .customTextContentLayout(
+                        .hideTextIfDoesNotFit(
+                            text = text,
+                            textStyle = textStyle,
+                            textMeasurer = textMeasurer,
                             maxTextWidth = maxTextWidth,
                             startPadding = startPadding,
                             endPadding = endPadding,
-                        ) { constraintWidth ->
-                            val intrinsicWidth =
-                                textMeasurer.measure(text, textStyle, softWrap = false).size.width
-                            intrinsicWidth <= constraintWidth
-                        }
+                        )
                         .neverDecreaseWidth(),
             )
         }
@@ -108,7 +102,6 @@
         }
 
         is OngoingActivityChipModel.Active.Text -> {
-            var hasOverflow by remember { mutableStateOf(false) }
             val text = viewModel.text
             Text(
                 text = text,
@@ -116,24 +109,14 @@
                 style = textStyle,
                 softWrap = false,
                 modifier =
-                    modifier
-                        .customTextContentLayout(
-                            maxTextWidth = maxTextWidth,
-                            startPadding = startPadding,
-                            endPadding = endPadding,
-                        ) { constraintWidth ->
-                            val intrinsicWidth =
-                                textMeasurer.measure(text, textStyle, softWrap = false).size.width
-                            hasOverflow = intrinsicWidth > constraintWidth
-                            constraintWidth.toFloat() / intrinsicWidth.toFloat() > 0.5f
-                        }
-                        .overflowFadeOut(
-                            hasOverflow = { hasOverflow },
-                            fadeLength =
-                                dimensionResource(
-                                    id = R.dimen.ongoing_activity_chip_text_fading_edge_length
-                                ),
-                        ),
+                    modifier.hideTextIfDoesNotFit(
+                        text = text,
+                        textStyle = textStyle,
+                        textMeasurer = textMeasurer,
+                        maxTextWidth = maxTextWidth,
+                        startPadding = startPadding,
+                        endPadding = endPadding,
+                    ),
             )
         }
 
@@ -180,45 +163,67 @@
 }
 
 /**
- * A custom layout modifier for text that ensures its text is only visible if a provided
- * [shouldShow] callback returns true. Imposes a provided [maxTextWidthPx]. Also, accounts for
- * provided padding values if provided and ensures its text is placed with the provided padding
- * included around it.
+ * A custom layout modifier for text that ensures the text is only visible if it completely fits
+ * within the constrained bounds. Imposes a provided [maxTextWidthPx]. Also, accounts for provided
+ * padding values if provided and ensures its text is placed with the provided padding included
+ * around it.
  */
-private fun Modifier.customTextContentLayout(
+private fun Modifier.hideTextIfDoesNotFit(
+    text: String,
+    textStyle: TextStyle,
+    textMeasurer: TextMeasurer,
     maxTextWidth: Dp,
     startPadding: Dp = 0.dp,
     endPadding: Dp = 0.dp,
-    shouldShow: (constraintWidth: Int) -> Boolean,
 ): Modifier {
     return this.then(
-        CustomTextContentLayoutElement(maxTextWidth, startPadding, endPadding, shouldShow)
+        HideTextIfDoesNotFitElement(
+            text,
+            textStyle,
+            textMeasurer,
+            maxTextWidth,
+            startPadding,
+            endPadding,
+        )
     )
 }
 
-private data class CustomTextContentLayoutElement(
+private data class HideTextIfDoesNotFitElement(
+    val text: String,
+    val textStyle: TextStyle,
+    val textMeasurer: TextMeasurer,
     val maxTextWidth: Dp,
     val startPadding: Dp,
     val endPadding: Dp,
-    val shouldShow: (constrainedWidth: Int) -> Boolean,
-) : ModifierNodeElement<CustomTextContentLayoutNode>() {
-    override fun create(): CustomTextContentLayoutNode {
-        return CustomTextContentLayoutNode(maxTextWidth, startPadding, endPadding, shouldShow)
+) : ModifierNodeElement<HideTextIfDoesNotFitNode>() {
+    override fun create(): HideTextIfDoesNotFitNode {
+        return HideTextIfDoesNotFitNode(
+            text,
+            textStyle,
+            textMeasurer,
+            maxTextWidth,
+            startPadding,
+            endPadding,
+        )
     }
 
-    override fun update(node: CustomTextContentLayoutNode) {
-        node.shouldShow = shouldShow
+    override fun update(node: HideTextIfDoesNotFitNode) {
+        node.text = text
+        node.textStyle = textStyle
+        node.textMeasurer = textMeasurer
         node.maxTextWidth = maxTextWidth
         node.startPadding = startPadding
         node.endPadding = endPadding
     }
 }
 
-private class CustomTextContentLayoutNode(
+private class HideTextIfDoesNotFitNode(
+    var text: String,
+    var textStyle: TextStyle,
+    var textMeasurer: TextMeasurer,
     var maxTextWidth: Dp,
     var startPadding: Dp,
     var endPadding: Dp,
-    var shouldShow: (constrainedWidth: Int) -> Boolean,
 ) : Modifier.Node(), LayoutModifierNode {
     override fun MeasureScope.measure(
         measurable: Measurable,
@@ -230,9 +235,10 @@
                 .coerceAtLeast(constraints.minWidth)
         val placeable = measurable.measure(constraints.copy(maxWidth = maxWidth))
 
-        val height = placeable.height
-        val width = placeable.width
-        return if (shouldShow(maxWidth)) {
+        val intrinsicWidth = textMeasurer.measure(text, textStyle, softWrap = false).size.width
+        return if (intrinsicWidth <= maxWidth) {
+            val height = placeable.height
+            val width = placeable.width
             layout(width + horizontalPadding.roundToPx(), height) {
                 placeable.place(startPadding.roundToPx(), 0)
             }
@@ -241,20 +247,3 @@
         }
     }
 }
-
-private fun Modifier.overflowFadeOut(hasOverflow: () -> Boolean, fadeLength: Dp): Modifier {
-    return graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen).drawWithCache {
-        val width = size.width
-        val start = (width - fadeLength.toPx()).coerceAtLeast(0f)
-        val gradient =
-            Brush.horizontalGradient(
-                colors = listOf(Color.Black, Color.Transparent),
-                startX = start,
-                endX = width,
-            )
-        onDrawWithContent {
-            drawContent()
-            if (hasOverflow()) drawRect(brush = gradient, blendMode = BlendMode.DstIn)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 76c5386..1cdf680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -19,6 +19,7 @@
 import android.content.res.ColorStateList
 import android.view.ViewGroup
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -103,6 +104,13 @@
         } else {
             dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
         }
+
+    val outline = model.colors.outline(context)
+    val outlineWidth = dimensionResource(R.dimen.ongoing_activity_chip_outline_width)
+
+    val shape =
+        RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))
+
     // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
     // height of the chip is determined by the height of the background of the Row below.
     Box(
@@ -121,12 +129,7 @@
             horizontalArrangement = Arrangement.Center,
             verticalAlignment = Alignment.CenterVertically,
             modifier =
-                Modifier.clip(
-                        RoundedCornerShape(
-                            dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
-                        )
-                    )
-                    .height(dimensionResource(R.dimen.ongoing_appops_chip_height))
+                Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
                     .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
                     .layout { measurable, constraints ->
                         val placeable = measurable.measure(constraints)
@@ -136,7 +139,14 @@
                             }
                         }
                     }
-                    .background(Color(model.colors.background(context).defaultColor))
+                    .background(Color(model.colors.background(context).defaultColor), shape = shape)
+                    .thenIf(outline != null) {
+                        Modifier.border(
+                            width = outlineWidth,
+                            color = Color(outline!!),
+                            shape = shape,
+                        )
+                    }
                     .padding(
                         horizontal =
                             if (hasEmbeddedIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 25f90f9..4954cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -21,7 +21,6 @@
 import androidx.annotation.ColorInt
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 
 /** Model representing how the chip in the status bar should be colored. */
 sealed interface ColorsModel {
@@ -31,13 +30,38 @@
     /** The color for the text (and icon) on the chip. */
     @ColorInt fun text(context: Context): Int
 
-    /** The chip should match the theme's primary color. */
-    data object Themed : ColorsModel {
+    /** The color to use for the chip outline, or null if the chip shouldn't have an outline. */
+    @ColorInt fun outline(context: Context): Int?
+
+    /** The chip should match the theme's primary accent color. */
+    // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
+    // only gets updated when a different configuration change happens, like a rotation.
+    data object AccentThemed : ColorsModel {
         override fun background(context: Context): ColorStateList =
             Utils.getColorAttr(context, com.android.internal.R.attr.colorAccent)
 
         override fun text(context: Context) =
             Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+
+        override fun outline(context: Context) = null
+    }
+
+    /** The chip should match the system theme main color. */
+    // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
+    // only gets updated when a different configuration change happens, like a rotation.
+    data object SystemThemed : ColorsModel {
+        override fun background(context: Context): ColorStateList =
+            ColorStateList.valueOf(
+                context.getColor(com.android.internal.R.color.materialColorSurfaceDim)
+            )
+
+        override fun text(context: Context) =
+            context.getColor(com.android.internal.R.color.materialColorOnSurface)
+
+        override fun outline(context: Context) =
+            // Outline is required on the SystemThemed chip to guarantee the chip doesn't completely
+            // blend in with the background.
+            context.getColor(com.android.internal.R.color.materialColorOutlineVariant)
     }
 
     /** The chip should have the given background color and primary text color. */
@@ -46,6 +70,8 @@
             ColorStateList.valueOf(backgroundColorInt)
 
         override fun text(context: Context): Int = primaryTextColorInt
+
+        override fun outline(context: Context) = null
     }
 
     /** The chip should have a red background with white text. */
@@ -55,15 +81,7 @@
         }
 
         override fun text(context: Context) = context.getColor(android.R.color.white)
-    }
 
-    companion object {
-        /** Converts the promoted notification colors to a [Custom] colors model. */
-        fun PromotedNotificationContentModel.toCustomColorsModel(): Custom {
-            return Custom(
-                backgroundColorInt = this.colors.backgroundColor,
-                primaryTextColorInt = this.colors.primaryTextColor,
-            )
-        }
+        override fun outline(context: Context) = null
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
index 52495eb..c19b144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
@@ -51,9 +51,8 @@
     }
 
     /**
-     * Returns true if this view should show the text because there's enough room for a substantial
-     * amount of text, and returns false if this view should hide the text because the text is much
-     * too long.
+     * Returns true if this view should show the text because there's enough room for all the text,
+     * and returns false if this view should hide the text because not all of it fits.
      *
      * @param desiredTextWidthPx should be calculated by having the view measure itself with
      *   [unlimitedWidthMeasureSpec] and then sending its `measuredWidth` to this method. (This
@@ -82,9 +81,8 @@
             enforcedTextWidth = maxWidthBasedOnDimension
         }
 
-        // Only show the text if at least 50% of it can show. (Assume that if < 50% of the text will
-        // be visible, the text will be more confusing than helpful.)
-        return desiredTextWidthPx <= enforcedTextWidth * 2
+        // Only show the text if all of it can show
+        return desiredTextWidthPx <= enforcedTextWidth
     }
 
     private fun fetchMaxWidth() =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 3ba0ae3..1a30caf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -214,7 +214,6 @@
                     if (
                         secondaryChip is InternalChipModel.Active &&
                             StatusBarNotifChips.isEnabled &&
-                            !StatusBarChipsModernization.isEnabled &&
                             !isScreenReasonablyLarge
                     ) {
                         // If we have two showing chips and we don't have a ton of room
@@ -222,8 +221,10 @@
                         // possible so that we have the highest chance of showing both chips (as
                         // opposed to showing the primary chip with a lot of text and completely
                         // hiding the secondary chip).
-                        // Also: If StatusBarChipsModernization is enabled, then we'll do the
-                        // squishing in Compose instead.
+                        // TODO(b/392895330): If StatusBarChipsModernization is enabled, do the
+                        // squishing in Compose instead, and be smart about it (e.g. if we have
+                        // room for the first chip to show text and the second chip to be icon-only,
+                        // do that instead of always squishing both chips.)
                         InternalMultipleOngoingActivityChipsModel(
                             primaryChip.squish(),
                             secondaryChip.squish(),
@@ -237,24 +238,31 @@
 
     /** Squishes the chip down to the smallest content possible. */
     private fun InternalChipModel.Active.squish(): InternalChipModel.Active {
-        return when (model) {
+        return if (model.shouldSquish()) {
+            InternalChipModel.Active(this.type, this.model.toIconOnly())
+        } else {
+            this
+        }
+    }
+
+    private fun OngoingActivityChipModel.Active.shouldSquish(): Boolean {
+        return when (this) {
             // Icon-only is already maximum squished
-            is OngoingActivityChipModel.Active.IconOnly -> this
+            is OngoingActivityChipModel.Active.IconOnly,
             // Countdown shows just a single digit, so already maximum squished
-            is OngoingActivityChipModel.Active.Countdown -> this
-            // The other chips have icon+text, so we should hide the text
+            is OngoingActivityChipModel.Active.Countdown -> false
+            // The other chips have icon+text, so we can squish them by hiding text
             is OngoingActivityChipModel.Active.Timer,
             is OngoingActivityChipModel.Active.ShortTimeDelta,
-            is OngoingActivityChipModel.Active.Text ->
-                InternalChipModel.Active(this.type, this.model.toIconOnly())
+            is OngoingActivityChipModel.Active.Text -> true
         }
     }
 
     private fun OngoingActivityChipModel.Active.toIconOnly(): OngoingActivityChipModel.Active {
         // If this chip doesn't have an icon, then it only has text and we should continue showing
         // its text. (This is theoretically impossible because
-        // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon, but protect
-        // against it just in case.)
+        // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon and
+        // [shouldSquish] returns false for that model, but protect against it just in case.)
         val currentIcon = icon ?: return this
         return OngoingActivityChipModel.Active.IconOnly(
             key,
@@ -271,8 +279,38 @@
      */
     val chips: StateFlow<MultipleOngoingActivityChipsModel> =
         if (StatusBarChipsModernization.isEnabled) {
-            incomingChipBundle
-                .map { bundle -> rankChips(bundle) }
+            combine(
+                    incomingChipBundle.map { bundle -> rankChips(bundle) },
+                    isScreenReasonablyLarge,
+                ) { rankedChips, isScreenReasonablyLarge ->
+                    if (
+                        StatusBarNotifChips.isEnabled &&
+                            !isScreenReasonablyLarge &&
+                            rankedChips.active.filter { !it.isHidden }.size >= 2
+                    ) {
+                        // If we have at least two showing chips and we don't have a ton of room
+                        // (!isScreenReasonablyLarge), then we want to make both of them as small as
+                        // possible so that we have the highest chance of showing both chips (as
+                        // opposed to showing the first chip with a lot of text and completely
+                        // hiding the other chips).
+                        val squishedActiveChips =
+                            rankedChips.active.map {
+                                if (!it.isHidden && it.shouldSquish()) {
+                                    it.toIconOnly()
+                                } else {
+                                    it
+                                }
+                            }
+
+                        MultipleOngoingActivityChipsModel(
+                            active = squishedActiveChips,
+                            overflow = rankedChips.overflow,
+                            inactive = rankedChips.inactive,
+                        )
+                    } else {
+                        rankedChips
+                    }
+                }
                 .stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel())
         } else {
             MutableStateFlow(MultipleOngoingActivityChipsModel()).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 89cb420..9bc5231 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -69,7 +69,10 @@
         messagingStyle.conversationType =
             if (entry.ranking.channel.isImportantConversation)
                 Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
-            else Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+            else if (entry.ranking.isConversation)
+                Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+            else
+                Notification.MessagingStyle.CONVERSATION_TYPE_LEGACY
         entry.ranking.conversationShortcutInfo?.let { shortcutInfo ->
             logger.logAsyncTaskProgress(entry, "getting shortcut icon")
             messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
similarity index 61%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 3a9525c..37485fe 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 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,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.collection;
 
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
-    void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+/**
+ * Abstract class to represent notification section bundled by AI.
+ */
+public class BundleEntry extends PipelineEntry {
+
+    public class BundleEntryAdapter implements EntryAdapter {
+    }
 }
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
similarity index 61%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 3a9525c..b12b1c5 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 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,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.collection;
 
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
-    void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+/**
+ * Adapter interface for UI to get relevant info.
+ */
+public interface EntryAdapter {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 915057f..c8e3be4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -27,7 +27,7 @@
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
  */
-public abstract class ListEntry {
+public abstract class ListEntry extends PipelineEntry {
     private final String mKey;
     private final long mCreationTime;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 9dc651e..7dd82a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -270,6 +270,9 @@
         setRanking(ranking);
     }
 
+    public class NotifEntryAdapter implements EntryAdapter {
+    }
+
     @Override
     public NotificationEntry getRepresentativeEntry() {
         return this;
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
similarity index 61%
rename from core/java/com/android/internal/app/IAppOpsCallback.aidl
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
index 3a9525c..efedfef 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 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,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.collection;
 
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
-    void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+/**
+ * Class to represent a notification, group, or bundle in the pipeline.
+ */
+public class PipelineEntry {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
index 6ceeb6a..bcaf187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackOptionalModule.kt
@@ -26,7 +26,7 @@
  * This is meant to be bound in SystemUI variants with [NotificationStackScrollLayoutController].
  */
 @Module
-interface NotificationStackGoogleModule {
+interface NotificationStackModule {
     @Binds
     fun bindNotificationStackRebindingHider(
         impl: NotificationStackRebindingHiderImpl
@@ -35,7 +35,7 @@
 
 /** This is meant to be used by all SystemUI variants, also those without NSSL. */
 @Module
-interface NotificationStackModule {
+interface NotificationStackOptionalModule {
     @BindsOptionalOf
     fun bindOptionalOfNotificationStackRebindingHider(): NotificationStackRebindingHider
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index e10825b..34f4969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -121,7 +121,7 @@
         NotificationMemoryModule.class,
         NotificationStatsLoggerModule.class,
         NotificationsLogModule.class,
-        NotificationStackModule.class,
+        NotificationStackOptionalModule.class,
 })
 public interface NotificationsModule {
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index de113d3..ccc2dff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -49,7 +49,7 @@
 ) : Dumpable {
 
     private val tag = "AvalancheController"
-    private val debug = Compile.IS_DEBUG
+    private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
     var baseEntryMapStr: () -> String = { "baseEntryMapStr not initialized" }
 
     var enableAtRuntime = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 7c75983..777ffda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -231,6 +231,7 @@
     ) {
         // Icon binding must be called in this order
         updateImageView(icon, content.smallIcon)
+        icon?.setImageLevel(content.iconLevel)
         icon?.setBackgroundColor(Background.colorInt)
         icon?.originalIconColor = PrimaryText.colorInt
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index cd78722..39c7df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -96,6 +96,7 @@
         contentBuilder.wasPromotedAutomatically =
             notification.extras.getBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false)
         contentBuilder.smallIcon = notification.smallIconModel(imageModelProvider)
+        contentBuilder.iconLevel = notification.iconLevel
         contentBuilder.appName = notification.loadHeaderAppName(context)
         contentBuilder.subText = notification.subText()
         contentBuilder.time = notification.extractWhen()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index af5a820..38d41e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -38,6 +38,7 @@
      */
     val wasPromotedAutomatically: Boolean,
     val smallIcon: ImageModel?,
+    val iconLevel: Int,
     val appName: CharSequence?,
     val subText: CharSequence?,
     val shortCriticalText: String?,
@@ -67,6 +68,7 @@
     class Builder(val key: String) {
         var wasPromotedAutomatically: Boolean = false
         var smallIcon: ImageModel? = null
+        var iconLevel: Int = 0
         var appName: CharSequence? = null
         var subText: CharSequence? = null
         var time: When? = null
@@ -94,6 +96,7 @@
                 identity = Identity(key, style),
                 wasPromotedAutomatically = wasPromotedAutomatically,
                 smallIcon = smallIcon,
+                iconLevel = iconLevel,
                 appName = appName,
                 subText = subText,
                 shortCriticalText = shortCriticalText,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a2d563a..9bf0768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -47,6 +47,7 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.IndentingPrintWriter;
@@ -81,6 +82,8 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
+import com.android.internal.widget.ConversationLayout;
+import com.android.internal.widget.MessagingLayout;
 import com.android.systemui.Flags;
 import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.FalsingManager;
@@ -97,6 +100,7 @@
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -205,6 +209,7 @@
     private int mMaxSmallHeightBeforeN;
     private int mMaxSmallHeightBeforeP;
     private int mMaxSmallHeightBeforeS;
+    private int mMaxSmallHeightWithSummarization;
     private int mMaxSmallHeight;
     private int mMaxExpandedHeight;
     private int mMaxExpandedHeightForPromotedOngoing;
@@ -856,6 +861,8 @@
         int smallHeight;
 
         boolean isCallLayout = contractedView instanceof CallLayout;
+        boolean isMessagingLayout = contractedView instanceof MessagingLayout
+                || contractedView instanceof ConversationLayout;
 
         if (customView && beforeS && !mIsSummaryWithChildren) {
             if (beforeN) {
@@ -867,6 +874,10 @@
             }
         } else if (isCallLayout) {
             smallHeight = maxExpandedHeight;
+        } else if (NmSummarizationUiFlag.isEnabled()
+                && isMessagingLayout
+                && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) {
+            smallHeight = mMaxSmallHeightWithSummarization;
         } else {
             smallHeight = mMaxSmallHeight;
         }
@@ -2111,6 +2122,8 @@
             mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
                     R.dimen.notification_min_height);
         }
+        mMaxSmallHeightWithSummarization = NotificationUtils.getFontScaledHeight(mContext,
+                com.android.internal.R.dimen.notification_collapsed_height_with_summarization);
         mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_height);
         mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 73e8246..e311b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -55,6 +55,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
@@ -201,13 +202,13 @@
                 mNotifLayoutInflaterFactoryProvider,
                 mHeadsUpStyleProvider,
                 mLogger);
-
         result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
                 packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
         boolean isConversation = entry.getRanking().isConversation();
         Notification.MessagingStyle messagingStyle = null;
-        if (isConversation && (AsyncHybridViewInflation.isEnabled()
-                || LockscreenOtpRedaction.isSingleLineViewEnabled())) {
+        if (NmSummarizationUiFlag.isEnabled()
+                || (isConversation && (AsyncHybridViewInflation.isEnabled()
+                || LockscreenOtpRedaction.isSingleLineViewEnabled()))) {
             messagingStyle = mConversationProcessor
                     .processNotification(entry, builder, mLogger);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index 589e5b8..517fc3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.InflationException
+import com.android.systemui.statusbar.notification.NmSummarizationUiFlag
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -700,7 +701,7 @@
 
             // process conversations and extract the messaging style
             val messagingStyle =
-                if (entry.ranking.isConversation) {
+                if (NmSummarizationUiFlag.isEnabled || entry.ranking.isConversation) {
                     conversationProcessor.processNotification(entry, builder, logger)
                 } else null
 
@@ -730,9 +731,8 @@
                         builder = builder,
                         systemUiContext = systemUiContext,
                         redactText = false,
-                        summarization = entry.sbn.notification.extras.getCharSequence(
-                            EXTRA_SUMMARIZED_CONTENT,
-                        )
+                        summarization =
+                            entry.sbn.notification.extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT),
                     )
                 } else null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 58326db..fa4fe46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,6 +47,7 @@
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
 import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
 import com.android.systemui.statusbar.phone.ui.TintedIconManager;
@@ -79,7 +80,7 @@
 
     private TextView mCarrierLabel;
     private ImageView mMultiUserAvatar;
-    private BatteryMeterView mBatteryView;
+    @Nullable private BatteryMeterView mBatteryView;
     private StatusIconContainer mStatusIconContainer;
     private StatusBarUserSwitcherContainer mUserSwitcherContainer;
 
@@ -131,6 +132,11 @@
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
+        if (NewStatusBarIcons.isEnabled()) {
+            // When this flag is rolled forward, this whole view can be removed
+            mBatteryView.setVisibility(View.GONE);
+            mBatteryView = null;
+        }
         mCutoutSpace = findViewById(R.id.cutout_space_view);
         mStatusIconArea = findViewById(R.id.status_icon_area);
         mStatusIconContainer = findViewById(R.id.statusIcons);
@@ -259,7 +265,10 @@
                 mMultiUserAvatar.setVisibility(View.GONE);
             }
         }
-        mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+
+        if (mBatteryView != null) {
+            mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+        }
     }
 
     private void updateSystemIconsLayoutParams() {
@@ -442,7 +451,9 @@
 
     /** Should only be called from {@link KeyguardStatusBarViewController}. */
     void onThemeChanged(TintedIconManager iconManager) {
-        mBatteryView.setColorsFromContext(mContext);
+        if (mBatteryView != null) {
+            mBatteryView.setColorsFromContext(mContext);
+        }
         updateIconsAndTextColors(iconManager);
     }
 
@@ -450,7 +461,9 @@
     void onOverlayChanged() {
         final int carrierTheme = R.style.TextAppearance_StatusBar_Clock;
         mCarrierLabel.setTextAppearance(carrierTheme);
-        mBatteryView.updatePercentView();
+        if (mBatteryView != null) {
+            mBatteryView.updatePercentView();
+        }
 
         final int userSwitcherTheme = R.style.TextAppearance_StatusBar_UserChip;
         TextView userSwitcherName = mUserSwitcherContainer.findViewById(R.id.current_user_name);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 40245ae..de72154 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -34,10 +34,12 @@
 import android.util.MathUtils;
 import android.view.DisplayCutout;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.compose.ui.platform.ComposeView;
 import androidx.core.animation.Animator;
 import androidx.core.animation.AnimatorListenerAdapter;
 import androidx.core.animation.ValueAnimator;
@@ -62,6 +64,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
 import com.android.systemui.statusbar.disableflags.DisableStateTracker;
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
@@ -71,10 +74,13 @@
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor;
 import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
 import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventDefaultAnimator;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
 import com.android.systemui.statusbar.phone.ui.TintedIconManager;
+import com.android.systemui.statusbar.pipeline.battery.ui.binder.UnifiedBatteryViewBinder;
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -125,6 +131,7 @@
     private final StatusBarIconController mStatusBarIconController;
     private final TintedIconManager.Factory mTintedIconManagerFactory;
     private final BatteryMeterViewController mBatteryMeterViewController;
+    private final BatteryViewModel.Factory mBatteryViewModelFactory;
     private final ShadeViewStateProvider mShadeViewStateProvider;
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardBypassController mKeyguardBypassController;
@@ -145,7 +152,7 @@
     private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
     private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
 
-    private View mSystemIconsContainer;
+    private ViewGroup mSystemIconsContainer;
     private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
@@ -327,6 +334,7 @@
             StatusBarIconController statusBarIconController,
             TintedIconManager.Factory tintedIconManagerFactory,
             BatteryMeterViewController batteryMeterViewController,
+            BatteryViewModel.Factory batteryViewModelFactory,
             ShadeViewStateProvider shadeViewStateProvider,
             KeyguardStateController keyguardStateController,
             KeyguardBypassController bypassController,
@@ -360,6 +368,7 @@
         mStatusBarIconController = statusBarIconController;
         mTintedIconManagerFactory = tintedIconManagerFactory;
         mBatteryMeterViewController = batteryMeterViewController;
+        mBatteryViewModelFactory = batteryViewModelFactory;
         mShadeViewStateProvider = shadeViewStateProvider;
         mKeyguardStateController = keyguardStateController;
         mKeyguardBypassController = bypassController;
@@ -417,7 +426,9 @@
     protected void onInit() {
         super.onInit();
         mCarrierTextController.init();
-        mBatteryMeterViewController.init();
+        if (!NewStatusBarIcons.isEnabled()) {
+            mBatteryMeterViewController.init();
+        }
         if (isMigrationEnabled()) {
             KeyguardStatusBarViewBinder.bind(mView, mKeyguardStatusBarViewModel);
         }
@@ -469,6 +480,15 @@
                 mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
         collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
                 mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+        if (NewStatusBarIcons.isEnabled()) {
+            ComposeView batteryComposeView = new ComposeView(mContext);
+            UnifiedBatteryViewBinder.bind(
+                    batteryComposeView,
+                    mBatteryViewModelFactory,
+                    DarkIconInteractor.toIsAreaDark(mView.darkChangeFlow()));
+
+            mSystemIconsContainer.addView(batteryComposeView, -1);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 4d222fd..b2d3377 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -81,6 +81,9 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
+
+import dagger.Lazy;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
@@ -226,7 +229,7 @@
     private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
 
     static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
-    private final float mDefaultScrimAlpha;
+    private float mDefaultScrimAlpha;
 
     private float mRawPanelExpansionFraction;
     private float mPanelScrimMinFraction;
@@ -257,6 +260,7 @@
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     private final BlurConfig mBlurConfig;
+    private final Lazy<WindowRootViewBlurInteractor> mWindowRootViewBlurInteractor;
     private Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
@@ -339,14 +343,13 @@
             KeyguardInteractor keyguardInteractor,
             @Main CoroutineDispatcher mainDispatcher,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            BlurConfig blurConfig) {
+            BlurConfig blurConfig,
+            Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor) {
         mScrimStateListener = lightBarController::setScrimState;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
         mBlurConfig = blurConfig;
-        // All scrims default alpha need to match bouncer background alpha to make sure the
-        // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
-        mDefaultScrimAlpha =
-                Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
+        mWindowRootViewBlurInteractor = windowRootViewBlurInteractor;
+        mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -407,7 +410,7 @@
 
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
-            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager, mBlurConfig);
+            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
             states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
         }
@@ -485,6 +488,30 @@
                         Edge.Companion.create(Scenes.Communal, LOCKSCREEN),
                         Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)),
                 mGlanceableHubConsumer, mMainDispatcher);
+
+        if (Flags.bouncerUiRevamp()) {
+            collectFlow(behindScrim,
+                    mWindowRootViewBlurInteractor.get().isBlurCurrentlySupported(),
+                    this::handleBlurSupportedChanged);
+        }
+    }
+
+    private void updateDefaultScrimAlpha(float alpha) {
+        mDefaultScrimAlpha = alpha;
+        for (ScrimState state : ScrimState.values()) {
+            state.setDefaultScrimAlpha(mDefaultScrimAlpha);
+        }
+        applyAndDispatchState();
+    }
+
+    private void handleBlurSupportedChanged(boolean isBlurSupported) {
+        if (isBlurSupported) {
+            updateDefaultScrimAlpha(TRANSPARENT_BOUNCER_SCRIM_ALPHA);
+            ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(mBlurConfig.getMaxBlurRadiusPx());
+        } else {
+            ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(0f);
+            updateDefaultScrimAlpha(BUSY_SCRIM_ALPHA);
+        }
     }
 
     // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 5f423cf..071a57a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -17,14 +17,12 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
-import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
 
 import android.graphics.Color;
 
 import com.android.app.tracing.coroutines.TrackTracer;
 import com.android.systemui.Flags;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.ui.transitions.BlurConfig;
 import com.android.systemui.res.R;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.ui.ShadeColors;
@@ -116,8 +114,8 @@
         @Override
         public void prepare(ScrimState previousState) {
             if (Flags.bouncerUiRevamp()) {
-                mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
-                mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+                mBehindAlpha = mDefaultScrimAlpha;
+                mNotifAlpha = 0f;
                 mBehindTint = mNotifTint = mSurfaceColor;
                 mFrontAlpha = 0f;
                 return;
@@ -153,12 +151,11 @@
                 if (previousState == SHADE_LOCKED) {
                     mBehindAlpha = previousState.getBehindAlpha();
                     mNotifAlpha = previousState.getNotifAlpha();
-                    mNotifBlurRadius = mBlurConfig.getMaxBlurRadiusPx();
                 } else {
                     mNotifAlpha = 0f;
                     mBehindAlpha = 0f;
                 }
-                mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+                mFrontAlpha = mDefaultScrimAlpha;
                 mFrontTint = mSurfaceColor;
                 return;
             }
@@ -403,7 +400,6 @@
     DozeParameters mDozeParameters;
     DockManager mDockManager;
     boolean mDisplayRequiresBlanking;
-    protected BlurConfig mBlurConfig;
     boolean mLaunchingAffordanceWithPreview;
     boolean mOccludeAnimationPlaying;
     boolean mWakeLockScreenSensorActive;
@@ -417,7 +413,7 @@
     protected float mNotifBlurRadius = 0.0f;
 
     public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
-            DockManager dockManager, BlurConfig blurConfig) {
+            DockManager dockManager) {
         mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
         mScrimInFront = scrimInFront;
         mScrimBehind = scrimBehind;
@@ -425,7 +421,6 @@
         mDozeParameters = dozeParameters;
         mDockManager = dockManager;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
-        mBlurConfig = blurConfig;
     }
 
     /** Prepare state for transition. */
@@ -536,4 +531,8 @@
     public float getNotifBlurRadius() {
         return mNotifBlurRadius;
     }
+
+    public void setNotifBlurRadius(float value) {
+        mNotifBlurRadius = value;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index e33baf7..ded964d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -57,11 +57,11 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.CommandQueue;
@@ -76,11 +76,11 @@
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.wmshell.BubblesManager;
 
@@ -115,7 +115,6 @@
     private final static String TAG = "StatusBarNotificationActivityStarter";
 
     private final Context mContext;
-    private final int mDisplayId;
 
     private final Handler mMainThreadHandler;
     private final Executor mUiBgExecutor;
@@ -155,8 +154,7 @@
 
     @Inject
     StatusBarNotificationActivityStarter(
-            Context context,
-            @DisplayId int displayId,
+            @ShadeDisplayAware Context context,
             Handler mainThreadHandler,
             @Background Executor uiBgExecutor,
             NotificationVisibilityProvider visibilityProvider,
@@ -189,7 +187,6 @@
             PowerInteractor powerInteractor,
             UserTracker userTracker) {
         mContext = context;
-        mDisplayId = displayId;
         mMainThreadHandler = mainThreadHandler;
         mUiBgExecutor = uiBgExecutor;
         mVisibilityProvider = visibilityProvider;
@@ -493,6 +490,7 @@
             boolean animate,
             boolean isActivityIntent) {
         mLogger.logStartNotificationIntent(entry);
+        final int displayId = mContext.getDisplayId();
         try {
             ActivityTransitionAnimator.Controller animationController =
                     new StatusBarTransitionAnimatorController(
@@ -501,7 +499,7 @@
                             mShadeController,
                             mNotificationShadeWindowController,
                             mCommandQueue,
-                            mDisplayId,
+                            displayId,
                             isActivityIntent);
             mActivityTransitionAnimator.startPendingIntentWithAnimation(
                     animationController,
@@ -511,11 +509,11 @@
                         long eventTime = row.getAndResetLastActionUpTime();
                         Bundle options = eventTime > 0
                                 ? getActivityOptions(
-                                mDisplayId,
+                                displayId,
                                 adapter,
                                 mKeyguardStateController.isShowing(),
                                 eventTime)
-                                : getActivityOptions(mDisplayId, adapter);
+                                : getActivityOptions(displayId, adapter);
                         int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                 null, null, options);
                         mLogger.logSendPendingIntent(entry, intent, result);
@@ -533,6 +531,7 @@
     public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
             @NonNull ExpandableNotificationRow row) {
         boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
+        final int displayId = mContext.getDisplayId();
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
             public boolean onDismiss() {
@@ -544,7 +543,7 @@
                                     mShadeController,
                                     mNotificationShadeWindowController,
                                     mCommandQueue,
-                                    mDisplayId,
+                                    displayId,
                                     true /* isActivityIntent */);
 
                     mActivityTransitionAnimator.startIntentWithAnimation(
@@ -552,7 +551,7 @@
                             (adapter) -> TaskStackBuilder.create(mContext)
                                     .addNextIntentWithParentStack(intent)
                                     .startActivities(getActivityOptions(
-                                                    mDisplayId,
+                                                    displayId,
                                                     adapter),
                                             new UserHandle(UserHandle.getUserId(appUid))));
                 });
@@ -571,6 +570,7 @@
     @Override
     public void startHistoryIntent(View view, boolean showHistory) {
         ModesEmptyShadeFix.assertInLegacyMode();
+        final int displayId = mContext.getDisplayId();
         boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
@@ -597,13 +597,13 @@
                                             mShadeController,
                                             mNotificationShadeWindowController,
                                             mCommandQueue,
-                                            mDisplayId,
+                                            displayId,
                                             true /* isActivityIntent */);
 
                     mActivityTransitionAnimator.startIntentWithAnimation(
                             animationController, animate, intent.getPackage(),
                             (adapter) -> tsb.startActivities(
-                                    getActivityOptions(mDisplayId, adapter),
+                                    getActivityOptions(displayId, adapter),
                                     mUserTracker.getUserHandle()));
                 });
                 return true;
@@ -620,6 +620,7 @@
 
     @Override
     public void startSettingsIntent(@NonNull View view, @NonNull SettingsIntent intentInfo) {
+        final int displayId = mContext.getDisplayId();
         boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
@@ -642,13 +643,13 @@
                                             mShadeController,
                                             mNotificationShadeWindowController,
                                             mCommandQueue,
-                                            mDisplayId,
+                                            displayId,
                                             true /* isActivityIntent */);
 
                     mActivityTransitionAnimator.startIntentWithAnimation(
                             animationController, animate, intentInfo.getTargetIntent().getPackage(),
                             (adapter) -> tsb.startActivities(
-                                    getActivityOptions(mDisplayId, adapter),
+                                    getActivityOptions(displayId, adapter),
                                     mUserTracker.getUserHandle()));
                 });
                 return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
index 7207d0a..4d531b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.qualifiers.DisplaySpecific;
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.core.NewStatusBarIcons;
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
 import com.android.systemui.statusbar.layout.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -85,7 +86,9 @@
     default void init() {
         // No one accesses these controllers, so we need to make sure we reference them here so they
         // do get initialized.
-        getBatteryMeterViewController().init();
+        if (!NewStatusBarIcons.isEnabled()) {
+            getBatteryMeterViewController().init();
+        }
         getHeadsUpAppearanceController().init();
         getPhoneStatusBarViewController().init();
         if (!NotificationsLiveDataStoreRefactor.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
index 903844e..9665c33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/binder/UnifiedBatteryViewBinder.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.statusbar.pipeline.battery.ui.binder
 
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.ViewCompositionStrategy
 import androidx.core.view.isVisible
@@ -27,6 +30,8 @@
 import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
 import com.android.systemui.statusbar.pipeline.battery.ui.composable.UnifiedBattery
 import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_HEIGHT
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_WIDTH
 import kotlinx.coroutines.flow.Flow
 
 /** In cases where the battery needs to be bound to an existing android view */
@@ -47,7 +52,13 @@
                     )
                     setContent {
                         val isDark by isAreaDark.collectAsStateWithLifecycle(IsAreaDark { true })
-                        UnifiedBattery(viewModelFactory = viewModelFactory, isDark = isDark)
+                        UnifiedBattery(
+                            modifier =
+                                Modifier.height(STATUS_BAR_BATTERY_HEIGHT)
+                                    .width(STATUS_BAR_BATTERY_WIDTH),
+                            viewModelFactory = viewModelFactory,
+                            isDark = isDark,
+                        )
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
index 2ee86ee..ac793a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/BatteryWithEstimate.kt
@@ -54,7 +54,7 @@
         )
         if (showEstimate) {
             viewModel.batteryTimeRemainingEstimate?.let {
-                Spacer(modifier.width(2.dp))
+                Spacer(modifier.width(4.dp))
                 Text(text = it, color = Color.White)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
index d0d099e7..afd4bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import androidx.compose.runtime.getValue
+import androidx.compose.ui.unit.dp
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -223,6 +224,10 @@
     }
 
     companion object {
+        // Status bar battery height, based on a 21x12 base canvas
+        val STATUS_BAR_BATTERY_HEIGHT = 13.dp
+        val STATUS_BAR_BATTERY_WIDTH = 22.75.dp
+
         fun Int.glyphRepresentation(): List<BatteryGlyph> = toString().map { it.toGlyph() }
 
         private fun Char.toGlyph(): BatteryGlyph =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 9d72daf..c34fa46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -23,6 +23,8 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
@@ -34,9 +36,11 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.theme.PlatformTheme
 import com.android.keyguard.AlphaOptimizedLinearLayout
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
+import com.android.systemui.statusbar.core.NewStatusBarIcons
 import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
 import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
@@ -51,6 +55,9 @@
 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ui.DarkIconManager
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.pipeline.battery.ui.composable.UnifiedBattery
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_HEIGHT
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel.Companion.STATUS_BAR_BATTERY_WIDTH
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
@@ -73,14 +80,13 @@
 ) {
     fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
         val composeView = ComposeView(root.context)
-        val displayId = root.context.displayId
         val darkIconDispatcher =
             darkIconDispatcherStore.forDisplay(root.context.displayId) ?: return composeView
         composeView.apply {
             setContent {
                 StatusBarRoot(
                     parent = root,
-                    statusBarViewModel = homeStatusBarViewModelFactory.create(displayId),
+                    statusBarViewModelFactory = homeStatusBarViewModelFactory,
                     statusBarViewBinder = homeStatusBarViewBinder,
                     notificationIconsBinder = notificationIconsBinder,
                     darkIconManagerFactory = darkIconManagerFactory,
@@ -110,7 +116,7 @@
 @Composable
 fun StatusBarRoot(
     parent: ViewGroup,
-    statusBarViewModel: HomeStatusBarViewModel,
+    statusBarViewModelFactory: HomeStatusBarViewModelFactory,
     statusBarViewBinder: HomeStatusBarViewBinder,
     notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
     darkIconManagerFactory: DarkIconManager.Factory,
@@ -120,6 +126,10 @@
     eventAnimationInteractor: SystemStatusEventAnimationInteractor,
     onViewCreated: (ViewGroup) -> Unit,
 ) {
+    val displayId = parent.context.displayId
+    val statusBarViewModel =
+        rememberViewModel("HomeStatusBar") { statusBarViewModelFactory.create(displayId) }
+
     Box(Modifier.fillMaxSize()) {
         // TODO(b/364360986): remove this before rolling the flag forward
         if (StatusBarRootModernization.SHOW_DISAMBIGUATION) {
@@ -159,10 +169,6 @@
                                         LinearLayout.LayoutParams.WRAP_CONTENT,
                                     )
 
-                                setViewCompositionStrategy(
-                                    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
-                                )
-
                                 setContent {
                                     PlatformTheme {
                                         val chips by
@@ -241,6 +247,12 @@
                         endSideContent.addView(composeView, 0)
                     }
 
+                    // If the flag is enabled, create and add a compose battery view to the end
+                    // of the system_icons container
+                    if (NewStatusBarIcons.isEnabled) {
+                        addBatteryComposable(phoneStatusBarView, statusBarViewModel)
+                    }
+
                     notificationIconsBinder.bindWhileAttached(
                         notificationIconContainer,
                         context.displayId,
@@ -263,6 +275,27 @@
     }
 }
 
+/** Create a new [UnifiedBattery] and add it to the end of the system_icons container */
+private fun addBatteryComposable(
+    phoneStatusBarView: PhoneStatusBarView,
+    statusBarViewModel: HomeStatusBarViewModel,
+) {
+    val batteryComposeView =
+        ComposeView(phoneStatusBarView.context).apply {
+            setContent {
+                UnifiedBattery(
+                    modifier =
+                        Modifier.height(STATUS_BAR_BATTERY_HEIGHT).width(STATUS_BAR_BATTERY_WIDTH),
+                    viewModelFactory = statusBarViewModel.batteryViewModelFactory,
+                    isDark = statusBarViewModel.areaDark,
+                )
+            }
+        }
+    phoneStatusBarView.findViewById<ViewGroup>(R.id.system_icons).apply {
+        addView(batteryComposeView, -1)
+    }
+}
+
 /**
  * This is our analog of the flexi "ribbon", which just shows some text so we know if the flag is on
  */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 1bc45a9..f396cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -19,6 +19,7 @@
 import android.annotation.ColorInt
 import android.graphics.Rect
 import android.view.View
+import androidx.compose.runtime.getValue
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -28,6 +29,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.plugins.DarkIconDispatcher
@@ -55,8 +58,10 @@
 import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
 import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
 import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
@@ -90,6 +95,9 @@
  * so that it's all in one place and easily testable outside of the fragment.
  */
 interface HomeStatusBarViewModel {
+    /** Factory to create the view model for the battery icon */
+    val batteryViewModelFactory: BatteryViewModel.Factory
+
     /**
      * True if the device is currently transitioning from lockscreen to occluded and false
      * otherwise.
@@ -171,6 +179,9 @@
      */
     val areaTint: Flow<StatusBarTintColor>
 
+    /** [IsAreaDark] applicable for this status bar's display and content area */
+    val areaDark: IsAreaDark
+
     /** Interface for the assisted factory, to allow for providing a fake in tests */
     interface HomeStatusBarViewModelFactory {
         fun create(displayId: Int): HomeStatusBarViewModel
@@ -181,6 +192,7 @@
 @AssistedInject
 constructor(
     @Assisted thisDisplayId: Int,
+    override val batteryViewModelFactory: BatteryViewModel.Factory,
     tableLoggerFactory: TableLogBufferFactory,
     homeStatusBarInteractor: HomeStatusBarInteractor,
     homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
@@ -201,7 +213,9 @@
     statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
     @Background bgScope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
-) : HomeStatusBarViewModel {
+) : HomeStatusBarViewModel, ExclusiveActivatable() {
+
+    private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
 
     val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
 
@@ -294,6 +308,13 @@
             .distinctUntilChanged()
             .flowOn(bgDispatcher)
 
+    override val areaDark: IsAreaDark by
+        hydrator.hydratedStateOf(
+            traceName = "areaDark",
+            initialValue = IsAreaDark { true },
+            source = darkIconInteractor.isAreaDark(thisDisplayId),
+        )
+
     /**
      * True if the current SysUI state can show the home status bar (aka this status bar), and false
      * if we shouldn't be showing any part of the home status bar.
@@ -473,6 +494,10 @@
     @View.Visibility
     private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
 
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
+
     /** Inject this to create the display-dependent view model */
     @AssistedFactory
     interface HomeStatusBarViewModelFactoryImpl :
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index cd401d5..e1640cd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -214,7 +214,12 @@
 
     private suspend fun waitForScreenTurnedOn() {
         traceAsync(TAG, "waitForScreenTurnedOn()") {
-            powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+            // dropping first as it's stateFlow and will always emit latest value but we're
+            // only interested in new states
+            powerInteractor.screenPowerState
+                .drop(1)
+                .filter { it == ScreenPowerState.SCREEN_ON }
+                .first()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
index 6ac0bb1..91f1426 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
@@ -50,6 +50,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
@@ -160,7 +161,12 @@
 
     private suspend fun waitForScreenTurnedOn() {
         traceAsync(TAG, "waitForScreenTurnedOn()") {
-            powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+            // dropping first as it's stateFlow and will always emit latest value but we're
+            // only interested in new states
+            powerInteractor.screenPowerState
+                .drop(1)
+                .filter { it == ScreenPowerState.SCREEN_ON }
+                .first()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 68bffeef..4d547705 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -37,8 +37,6 @@
 import android.media.IVolumeController;
 import android.media.MediaRouter2Manager;
 import android.media.VolumePolicy;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.Token;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -61,6 +59,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.volume.MediaSessions;
+import com.android.settingslib.volume.MediaSessions.SessionId;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Flags;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -1402,12 +1401,13 @@
     }
 
     protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
-        private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+        private final HashMap<SessionId, Integer> mRemoteStreams = new HashMap<>();
 
         private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
 
         @Override
-        public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+        public void onRemoteUpdate(
+                    SessionId token, String name, MediaSessions.VolumeInfo volumeInfo) {
                 addStream(token, "onRemoteUpdate");
 
                 int stream = 0;
@@ -1415,14 +1415,15 @@
                     stream = mRemoteStreams.get(token);
                 }
                 Slog.d(TAG,
-                        "onRemoteUpdate: stream: " + stream + " volume: " + pi.getCurrentVolume());
+                        "onRemoteUpdate: stream: "
+                                + stream + " volume: " + volumeInfo.getCurrentVolume());
                 boolean changed = mState.states.indexOfKey(stream) < 0;
                 final StreamState ss = streamStateW(stream);
                 ss.dynamic = true;
                 ss.levelMin = 0;
-                ss.levelMax = pi.getMaxVolume();
-                if (ss.level != pi.getCurrentVolume()) {
-                    ss.level = pi.getCurrentVolume();
+                ss.levelMax = volumeInfo.getMaxVolume();
+                if (ss.level != volumeInfo.getCurrentVolume()) {
+                    ss.level = volumeInfo.getCurrentVolume();
                     changed = true;
                 }
                 if (!Objects.equals(ss.remoteLabel, name)) {
@@ -1437,11 +1438,11 @@
         }
 
         @Override
-        public void onRemoteVolumeChanged(Token token, int flags) {
-                addStream(token, "onRemoteVolumeChanged");
+        public void onRemoteVolumeChanged(SessionId sessionId, int flags) {
+                addStream(sessionId, "onRemoteVolumeChanged");
                 int stream = 0;
                 synchronized (mRemoteStreams) {
-                    stream = mRemoteStreams.get(token);
+                    stream = mRemoteStreams.get(sessionId);
                 }
                 final boolean showUI = shouldShowUI(flags);
                 Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
@@ -1459,7 +1460,7 @@
         }
 
         @Override
-        public void onRemoteRemoved(Token token) {
+        public void onRemoteRemoved(SessionId token) {
             int stream;
             synchronized (mRemoteStreams) {
                 if (!mRemoteStreams.containsKey(token)) {
@@ -1480,7 +1481,7 @@
         }
 
         public void setStreamVolume(int stream, int level) {
-            final Token token = findToken(stream);
+            final SessionId token = findToken(stream);
             if (token == null) {
                 Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
                 return;
@@ -1488,9 +1489,9 @@
             mMediaSessions.setVolume(token, level);
         }
 
-        private Token findToken(int stream) {
+        private SessionId findToken(int stream) {
             synchronized (mRemoteStreams) {
-                for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+                for (Map.Entry<SessionId, Integer> entry : mRemoteStreams.entrySet()) {
                     if (entry.getValue().equals(stream)) {
                         return entry.getKey();
                     }
@@ -1499,7 +1500,7 @@
             return null;
         }
 
-        private void addStream(Token token, String triggeringMethod) {
+        private void addStream(SessionId token, String triggeringMethod) {
             synchronized (mRemoteStreams) {
                 if (!mRemoteStreams.containsKey(token)) {
                     mRemoteStreams.put(token, mNextStream);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 83b7c18..86defff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -68,7 +68,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.volume_dialog)
-        requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached {
+        requireViewById<View>(R.id.volume_dialog).repeatWhenAttached {
             coroutineScopeTraced("[Volume]dialog") {
                 val component = componentFactory.create(this)
                 with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 20a74b0..afe3d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.volume.dialog.domain.interactor
 
 import android.annotation.SuppressLint
+import android.provider.Settings
 import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
@@ -28,8 +30,9 @@
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel.Visible
 import com.android.systemui.volume.dialog.utils.VolumeTracer
 import javax.inject.Inject
-import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
@@ -43,8 +46,6 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
-private val MAX_DIALOG_SHOW_TIME: Duration = 3.seconds
-
 /**
  * Handles Volume Dialog visibility state. It might change from several sources:
  * - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
@@ -60,8 +61,11 @@
     private val tracer: VolumeTracer,
     private val repository: VolumeDialogVisibilityRepository,
     private val controller: VolumeDialogController,
+    private val secureSettingsRepository: SecureSettingsRepository,
 ) {
 
+    private val defaultTimeout = 3.seconds
+
     @SuppressLint("SharedFlowCreation")
     private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
     val dialogVisibility: Flow<VolumeDialogVisibilityModel> =
@@ -73,7 +77,14 @@
     init {
         merge(
                 mutableDismissDialogEvents.mapLatest {
-                    delay(MAX_DIALOG_SHOW_TIME)
+                    delay(
+                        secureSettingsRepository
+                            .getInt(
+                                Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
+                                defaultTimeout.toInt(DurationUnit.MILLISECONDS),
+                            )
+                            .milliseconds
+                    )
                     VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
                 },
                 callbacksInteractor.event,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 3d0c7d6..eb2b2f6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -120,7 +120,7 @@
         drawerContainer.setTransitionListener(ringerDrawerTransitionListener)
         volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate()
         ringerBackgroundView.background = ringerBackgroundView.background.mutate()
-        launch { dialogViewModel.addTouchableBounds(drawerContainer) }
+        launch { dialogViewModel.addTouchableBounds(ringerBackgroundView) }
 
         viewModel.ringerViewModel
             .mapLatest { ringerState ->
@@ -246,16 +246,12 @@
                 uiModel.drawerState.currentMode != uiModel.drawerState.previousMode
         ) {
             val count = uiModel.availableButtons.size
-            val selectedButton =
-                getChildAt(count - uiModel.currentButtonIndex)
-                    .requireViewById<ImageButton>(R.id.volume_drawer_button)
+            val selectedButton = getChildAt(count - uiModel.currentButtonIndex) as ImageButton
             val previousIndex =
                 uiModel.availableButtons.indexOfFirst {
                     it.ringerMode == uiModel.drawerState.previousMode
                 }
-            val unselectedButton =
-                getChildAt(count - previousIndex)
-                    .requireViewById<ImageButton>(R.id.volume_drawer_button)
+            val unselectedButton = getChildAt(count - previousIndex) as ImageButton
             // We only need to execute on roundness animation end and volume dialog background
             // progress update once because these changes should be applied once on volume dialog
             // background and ringer drawer views.
@@ -306,7 +302,7 @@
     ) {
         val count = uiModel.availableButtons.size
         uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
-            val view = getChildAt(count - index)
+            val view = getChildAt(count - index) as ImageButton
             val isOpen = uiModel.drawerState is RingerDrawerState.Open
             if (index == uiModel.currentButtonIndex) {
                 view.bindDrawerButton(
@@ -323,37 +319,37 @@
         onAnimationEnd?.run()
     }
 
-    private fun View.bindDrawerButton(
+    private fun ImageButton.bindDrawerButton(
         buttonViewModel: RingerButtonViewModel,
         viewModel: VolumeDialogRingerDrawerViewModel,
         isOpen: Boolean,
         isSelected: Boolean = false,
         isAnimated: Boolean = false,
     ) {
+        // id = buttonViewModel.viewId
+        setSelected(isSelected)
         val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
-        with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
-            setImageResource(buttonViewModel.imageResId)
-            contentDescription =
-                if (isSelected && !isOpen) {
-                    context.getString(
-                        R.string.volume_ringer_drawer_closed_content_description,
-                        ringerContentDesc,
-                    )
-                } else {
-                    ringerContentDesc
-                }
-            if (isSelected && !isAnimated) {
-                setBackgroundResource(R.drawable.volume_drawer_selection_bg)
-                setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
-                background = background.mutate()
-            } else if (!isAnimated) {
-                setBackgroundResource(R.drawable.volume_ringer_item_bg)
-                setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
-                background = background.mutate()
+        setImageResource(buttonViewModel.imageResId)
+        contentDescription =
+            if (isSelected && !isOpen) {
+                context.getString(
+                    R.string.volume_ringer_drawer_closed_content_description,
+                    ringerContentDesc,
+                )
+            } else {
+                ringerContentDesc
             }
-            setOnClickListener {
-                viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
-            }
+        if (isSelected && !isAnimated) {
+            setBackgroundResource(R.drawable.volume_drawer_selection_bg)
+            setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
+            background = background.mutate()
+        } else if (!isAnimated) {
+            setBackgroundResource(R.drawable.volume_ringer_item_bg)
+            setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
+            background = background.mutate()
+        }
+        setOnClickListener {
+            viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index a51e33a..2c9ee54 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -19,7 +19,6 @@
 import android.graphics.drawable.Drawable
 import android.view.View
 import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
@@ -61,11 +60,9 @@
 import javax.inject.Inject
 import kotlin.math.round
 import kotlin.math.roundToInt
-import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
 
 @VolumeDialogSliderScope
 class VolumeDialogSliderViewBinder
@@ -116,10 +113,6 @@
 
     val steps = with(sliderStateModel.valueRange) { endInclusive - start - 1 }.toInt()
 
-    var animateJob: Job? = null
-    val animatedSliderValue =
-        remember(sliderStateModel.value) { Animatable(sliderStateModel.value) }
-
     val interactionSource = remember { MutableInteractionSource() }
     val hapticsViewModel: SliderHapticsViewModel? =
         hapticsViewModelFactory?.let {
@@ -149,16 +142,7 @@
                         hapticsViewModel?.onValueChangeEnded()
                     }
                     sliderState.onValueChange = { newValue ->
-                        if (newValue != animatedSliderValue.targetValue) {
-                            animateJob?.cancel()
-                            animateJob =
-                                coroutineScope.launch {
-                                    animatedSliderValue.animateTo(newValue) {
-                                        sliderState.value = value
-                                    }
-                                }
-                        }
-
+                        sliderState.value = newValue
                         hapticsViewModel?.addVelocityDataPoint(newValue)
                         overscrollViewModel.setSlider(
                             value = sliderState.value,
@@ -173,7 +157,7 @@
     var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderStateModel.value)) }
     LaunchedEffect(sliderStateModel.value) {
         val value = sliderStateModel.value
-        launch { animatedSliderValue.animateTo(value) }
+        sliderState.value = value
         if (value != lastDiscreteStep) {
             lastDiscreteStep = value
             hapticsViewModel?.onValueChange(value)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index f2d7d95..7cc4bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -74,7 +74,7 @@
         val insets: MutableStateFlow<WindowInsets> =
             MutableStateFlow(WindowInsets.Builder().build())
         // Root view of the Volume Dialog.
-        val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+        val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog)
 
         animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
 
diff --git a/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt
new file mode 100644
index 0000000..95b3b68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.window.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that can be installed in sysui variants where we support cross window blur.
+ */
+@Module
+interface WindowRootViewBlurModule {
+    @Binds
+    @SysUISingleton
+    fun bindWindowRootViewBlurRepository(
+        windowRootViewBlurRepositoryImpl: WindowRootViewBlurRepositoryImpl
+    ): WindowRootViewBlurRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt
new file mode 100644
index 0000000..ae917e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/dagger/WindowRootViewBlurNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.window.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.window.data.repository.NoopWindowRootViewBlurRepository
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that can be installed in sysui variants where we don't support cross window blur.
+ */
+@Module
+interface WindowRootViewBlurNotSupportedModule {
+    @Binds
+    @SysUISingleton
+    fun bindWindowRootViewBlurRepository(
+        windowRootViewBlurRepositoryImpl: NoopWindowRootViewBlurRepository
+    ): WindowRootViewBlurRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
new file mode 100644
index 0000000..80aa11a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/NoopWindowRootViewBlurRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 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.window.data.repository
+
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class NoopWindowRootViewBlurRepository @Inject constructor() : WindowRootViewBlurRepository {
+    override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
+    override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val isBlurSupported: StateFlow<Boolean> = MutableStateFlow(false)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
index 22a74c8..41ceda0 100644
--- a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
@@ -16,14 +16,77 @@
 
 package com.android.systemui.window.data.repository
 
+import android.app.ActivityManager
+import android.os.SystemProperties
+import android.view.CrossWindowBlurListeners
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository.Companion.isDisableBlurSysPropSet
+import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
 
 /** Repository that maintains state for the window blur effect. */
-@SysUISingleton
-class WindowRootViewBlurRepository @Inject constructor() {
-    val blurRadius = MutableStateFlow(0)
+interface WindowRootViewBlurRepository {
+    val blurRadius: MutableStateFlow<Int>
+    val isBlurOpaque: MutableStateFlow<Boolean>
 
-    val isBlurOpaque = MutableStateFlow(false)
+    /** Is blur supported based on settings toggle and battery power saver mode. */
+    val isBlurSupported: StateFlow<Boolean>
+
+    companion object {
+        /**
+         * Whether the `persist.sysui.disableBlur` is set, this is used to disable blur for tests.
+         */
+        @JvmStatic
+        fun isDisableBlurSysPropSet() = SystemProperties.getBoolean(DISABLE_BLUR_PROPERTY, false)
+
+        // property that can be used to disable the cross window blur for tests
+        private const val DISABLE_BLUR_PROPERTY = "persist.sysui.disableBlur"
+    }
+}
+
+@SysUISingleton
+class WindowRootViewBlurRepositoryImpl
+@Inject
+constructor(
+    crossWindowBlurListeners: CrossWindowBlurListeners,
+    @Main private val executor: Executor,
+    @Application private val scope: CoroutineScope,
+) : WindowRootViewBlurRepository {
+    override val blurRadius = MutableStateFlow(0)
+
+    override val isBlurOpaque = MutableStateFlow(false)
+
+    override val isBlurSupported: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val sendUpdate = { value: Boolean ->
+                    trySendWithFailureLogging(
+                        isBlurAllowed() && value,
+                        TAG,
+                        "unable to send blur enabled/disable state change",
+                    )
+                }
+                crossWindowBlurListeners.addListener(executor, sendUpdate)
+                sendUpdate(crossWindowBlurListeners.isCrossWindowBlurEnabled)
+
+                awaitClose { crossWindowBlurListeners.removeListener(sendUpdate) }
+            } // stateIn because this is backed by a binder call.
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    private fun isBlurAllowed(): Boolean {
+        return ActivityManager.isHighEndGfx() && !isDisableBlurSysPropSet()
+    }
+
+    companion object {
+        const val TAG = "WindowRootViewBlurRepository"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
index 9e36934..7a88a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
@@ -75,6 +75,12 @@
         _onBlurAppliedEvent.emit(appliedBlurRadius)
     }
 
+    /**
+     * Whether blur is enabled or not based on settings toggle, critical thermal state, battery save
+     * state and multimedia tunneling state.
+     */
+    val isBlurCurrentlySupported: StateFlow<Boolean> = repository.isBlurSupported
+
     /** Radius of blur to be applied on the window root view. */
     val blurRadius: StateFlow<Int> = repository.blurRadius.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
index b18c39dc..1b42352 100644
--- a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
@@ -26,11 +26,14 @@
 import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
@@ -38,6 +41,7 @@
 typealias BlurAppliedUiEvent = Int
 
 /** View model for window root view. */
+@OptIn(ExperimentalCoroutinesApi::class)
 class WindowRootViewModel
 @AssistedInject
 constructor(
@@ -58,7 +62,7 @@
             glanceableHubTransitions.map { it.windowBlurRadius.logIfPossible(it.javaClass.name) }
         else emptyList()
 
-    val blurRadius: Flow<Float> =
+    private val _blurRadius =
         listOf(
                 *bouncerBlurRadiusFlows.toTypedArray(),
                 *glanceableHubBlurRadiusFlows.toTypedArray(),
@@ -66,8 +70,23 @@
             )
             .merge()
 
+    val blurRadius: Flow<Float> =
+        blurInteractor.isBlurCurrentlySupported.flatMapLatest { blurSupported ->
+            if (blurSupported) {
+                _blurRadius
+            } else {
+                flowOf(0f)
+            }
+        }
+
     val isBlurOpaque =
-        blurInteractor.isBlurOpaque.distinctUntilChanged().logIfPossible("isBlurOpaque")
+        blurInteractor.isBlurCurrentlySupported.flatMapLatest { blurSupported ->
+            if (blurSupported) {
+                blurInteractor.isBlurOpaque.distinctUntilChanged().logIfPossible("isBlurOpaque")
+            } else {
+                flowOf(false)
+            }
+        }
 
     override suspend fun onActivated(): Nothing {
         coroutineScope {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 732561e0..944604f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -640,11 +640,11 @@
             }
         }
 
-    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun onTouchEvent_shadeInteracting_movesNotDispatched() =
         with(kosmos) {
             testScope.runTest {
+                `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
                 // On lockscreen.
                 goToScene(CommunalScenes.Blank)
                 whenever(
@@ -721,11 +721,11 @@
             }
         }
 
-    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
         with(kosmos) {
             testScope.runTest {
+                `whenever`(communalViewModel.swipeToHubEnabled()).thenReturn(true)
                 // On lockscreen.
                 goToScene(CommunalScenes.Blank)
                 whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index edb0f35..f3af794f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -59,6 +59,7 @@
 import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -202,6 +203,7 @@
                 Lazy { kosmos.shadeDisplaysRepository },
                 variableDateViewControllerFactory,
                 batteryMeterViewController,
+                kosmos.batteryViewModelFactory,
                 dumpManager,
                 mShadeCarrierGroupControllerBuilder,
                 combinedShadeHeadersConstraintManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a523488..9137215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
@@ -118,10 +119,8 @@
     @Rule public Expect mExpect = Expect.create();
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
 
-    private final FakeConfigurationController mConfigurationController =
-            new FakeConfigurationController();
-    private final LargeScreenShadeInterpolator
-            mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+    private FakeConfigurationController mConfigurationController;
+    private LargeScreenShadeInterpolator mLinearLargeScreenShadeInterpolator;
 
     private final TestScope mTestScope = mKosmos.getTestScope();
     private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -137,6 +136,7 @@
     private boolean mAlwaysOnEnabled;
     private TestableLooper mLooper;
     private Context mContext;
+
     @Mock private DozeParameters mDozeParameters;
     @Mock private LightBarController mLightBarController;
     @Mock private DelayedWakeLock.Factory mDelayedWakeLockFactory;
@@ -149,12 +149,11 @@
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     @Mock private AlternateBouncerToGoneTransitionViewModel
             mAlternateBouncerToGoneTransitionViewModel;
-    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor =
-            mKosmos.getKeyguardTransitionInteractor();
-    private final FakeKeyguardTransitionRepository mKeyguardTransitionRepository =
-            mKosmos.getKeyguardTransitionRepository();
     @Mock private KeyguardInteractor mKeyguardInteractor;
 
+    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private FakeKeyguardTransitionRepository mKeyguardTransitionRepository;
+
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
     //   event-dispatch-on-registration pattern caused some of these unit tests to fail.)
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -238,6 +237,9 @@
         when(mContext.getColor(com.android.internal.R.color.materialColorSurface))
                 .thenAnswer(invocation -> mSurfaceColor);
 
+        mConfigurationController = new FakeConfigurationController();
+        mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+
         mScrimBehind = spy(new ScrimView(mContext));
         mScrimInFront = new ScrimView(mContext);
         mNotificationsScrim = new ScrimView(mContext);
@@ -270,6 +272,9 @@
         when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
                 .thenReturn(emptyFlow());
 
+        mKeyguardTransitionRepository = mKosmos.getKeyguardTransitionRepository();
+        mKeyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor();
+
         mScrimController = new ScrimController(
                 mLightBarController,
                 mDozeParameters,
@@ -290,7 +295,8 @@
                 mKeyguardInteractor,
                 mKosmos.getTestDispatcher(),
                 mLinearLargeScreenShadeInterpolator,
-                new BlurConfig(0.0f, 0.0f));
+                new BlurConfig(0.0f, 0.0f),
+                mKosmos::getWindowRootViewBlurInteractor);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -321,6 +327,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToKeyguard() {
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
@@ -336,7 +343,7 @@
     }
 
     @Test
-    public void transitionToShadeLocked() {
+@DisableSceneContainer void transitionToShadeLocked() {
         mScrimController.legacyTransitionTo(SHADE_LOCKED);
         mScrimController.setQsPosition(1f, 0);
         finishAnimationsImmediately();
@@ -372,6 +379,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToShadeLocked_clippingQs() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -390,6 +398,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToOff() {
         mScrimController.legacyTransitionTo(ScrimState.OFF);
         finishAnimationsImmediately();
@@ -405,6 +414,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToAod_withRegularWallpaper() {
         mScrimController.legacyTransitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
@@ -420,6 +430,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToAod_withFrontAlphaUpdates() {
         // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -464,6 +475,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToAod_afterDocked_ignoresAlwaysOnAndUpdatesFrontAlpha() {
         // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -505,6 +517,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToPulsing_withFrontAlphaUpdates() {
         // Pre-condition
         // Need to go to AoD first because PULSING doesn't change
@@ -550,6 +563,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToKeyguardBouncer() {
         mScrimController.legacyTransitionTo(BOUNCER);
         finishAnimationsImmediately();
@@ -570,6 +584,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void lockscreenToHubTransition_setsBehindScrimAlpha() {
         // Start on lockscreen.
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -616,6 +631,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void hubToLockscreenTransition_setsViewAlpha() {
         // Start on glanceable hub.
         mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
@@ -662,6 +678,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToHub() {
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -676,6 +693,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void openBouncerOnHub() {
         mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
 
@@ -705,6 +723,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void openShadeOnHub() {
         mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
 
@@ -733,6 +752,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToHubOverDream() {
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -747,6 +767,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void openBouncerOnHubOverDream() {
         mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
 
@@ -776,6 +797,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void openShadeOnHubOverDream() {
         mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
 
@@ -804,6 +826,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
         assertEquals(BOUNCER.getBehindTint(), 0x112233);
         mSurfaceColor = 0x223344;
@@ -812,6 +835,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(BOUNCER);
@@ -824,6 +848,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToKeyguardBouncer_clippingQs() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(BOUNCER);
@@ -844,6 +869,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(BOUNCER);
@@ -866,6 +892,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
         mScrimController.setClipsQsScrim(false);
         mScrimController.legacyTransitionTo(BOUNCER);
@@ -888,6 +915,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToBouncer() {
         mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED);
         finishAnimationsImmediately();
@@ -901,6 +929,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToUnlocked_clippedQs() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.setRawPanelExpansionFraction(0f);
@@ -959,6 +988,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
         mScrimController.setClipsQsScrim(false);
         mScrimController.setRawPanelExpansionFraction(0f);
@@ -998,6 +1028,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void scrimStateCallback() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         finishAnimationsImmediately();
@@ -1013,6 +1044,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void panelExpansion() {
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1035,6 +1067,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void qsExpansion() {
         reset(mScrimBehind);
         mScrimController.setQsPosition(1f, 999 /* value doesn't matter */);
@@ -1047,6 +1080,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void qsExpansion_clippingQs() {
         reset(mScrimBehind);
         mScrimController.setClipsQsScrim(true);
@@ -1060,6 +1094,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void qsExpansion_half_clippingQs() {
         reset(mScrimBehind);
         mScrimController.setClipsQsScrim(true);
@@ -1073,6 +1108,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void panelExpansionAffectsAlpha() {
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1095,6 +1131,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToUnlockedFromOff() {
         // Simulate unlock with fingerprint without AOD
         mScrimController.legacyTransitionTo(ScrimState.OFF);
@@ -1117,6 +1154,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToUnlockedFromAod() {
         // Simulate unlock with fingerprint
         mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1139,6 +1177,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void scrimBlanksBeforeLeavingAod() {
         // Simulate unlock with fingerprint
         mScrimController.legacyTransitionTo(ScrimState.AOD);
@@ -1162,6 +1201,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void scrimBlankCallbackWhenUnlockingFromPulse() {
         boolean[] blanked = {false};
         // Simulate unlock with fingerprint
@@ -1180,6 +1220,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void blankingNotRequired_leavingAoD() {
         // GIVEN display does NOT need blanking
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
@@ -1204,7 +1245,8 @@
                 mKeyguardInteractor,
                 mKosmos.getTestDispatcher(),
                 mLinearLargeScreenShadeInterpolator,
-                new BlurConfig(0.0f, 0.0f));
+                new BlurConfig(0.0f, 0.0f),
+                mKosmos::getWindowRootViewBlurInteractor);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1234,6 +1276,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimCallback() {
         int[] callOrder = {0, 0, 0};
         int[] currentCall = {0};
@@ -1260,12 +1303,14 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimCallbacksWithoutAmbientDisplay() {
         mAlwaysOnEnabled = false;
         testScrimCallback();
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimCallbackCancelled() {
         boolean[] cancelledCalled = {false};
         mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() {
@@ -1279,6 +1324,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testHoldsWakeLock_whenAOD() {
         mScrimController.legacyTransitionTo(ScrimState.AOD);
         verify(mWakeLock).acquire(anyString());
@@ -1288,6 +1334,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testDoesNotHoldWakeLock_whenUnlocking() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         finishAnimationsImmediately();
@@ -1295,6 +1342,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testCallbackInvokedOnSameStateTransition() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         finishAnimationsImmediately();
@@ -1304,6 +1352,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testConservesExpansionOpacityAfterTransition() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         mScrimController.setRawPanelExpansionFraction(0.5f);
@@ -1321,6 +1370,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testCancelsOldAnimationBeforeBlanking() {
         mScrimController.legacyTransitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
@@ -1333,6 +1383,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimsAreNotFocusable() {
         assertFalse("Behind scrim should not be focusable", mScrimBehind.isFocusable());
         assertFalse("Front scrim should not be focusable", mScrimInFront.isFocusable());
@@ -1341,6 +1392,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testEatsTouchEvent() {
         HashSet<ScrimState> eatsTouches =
                 new HashSet<>(Collections.singletonList(ScrimState.AOD));
@@ -1357,6 +1409,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testAnimatesTransitionToAod() {
         when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         ScrimState.AOD.prepare(ScrimState.KEYGUARD);
@@ -1371,6 +1424,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testIsLowPowerMode() {
         HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
                 ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
@@ -1388,6 +1442,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimsOpaque_whenShadeFullyExpanded() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         mScrimController.setRawPanelExpansionFraction(1);
@@ -1402,6 +1457,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimsVisible_whenShadeVisible() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1417,6 +1473,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testDoesntAnimate_whenUnlocking() {
         // LightRevealScrim will animate the transition, we should only hide the keyguard scrims.
         ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
@@ -1437,6 +1494,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimsVisible_whenShadeVisible_clippingQs() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1452,6 +1510,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
         mScrimController.setQsPosition(0.25f, 300);
@@ -1463,6 +1522,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testNotificationScrimTransparent_whenOnLockscreen() {
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
         // even if shade is not pulled down, panel has expansion of 1 on the lockscreen
@@ -1475,6 +1535,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
         mScrimController.setRawPanelExpansionFraction(1);
         mScrimController.legacyTransitionTo(SHADE_LOCKED);
@@ -1486,6 +1547,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void qsExpansion_BehindTint_shadeLocked_bouncerActive_usesBouncerProgress() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         // clipping doesn't change tested logic but allows to assert scrims more in line with
@@ -1502,6 +1564,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
 
@@ -1518,6 +1581,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
@@ -1533,6 +1597,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
@@ -1552,6 +1617,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setClipsQsScrim(true);
@@ -1572,6 +1638,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
         mScrimController.setClipsQsScrim(true);
@@ -1592,6 +1659,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
         when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
         mScrimController.setClipsQsScrim(false);
@@ -1603,6 +1671,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testNotificationTransparency_followsTransitionToFullShade() {
         mScrimController.setClipsQsScrim(true);
 
@@ -1644,6 +1713,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationTransparency_followsNotificationScrimProgress() {
         mScrimController.legacyTransitionTo(SHADE_LOCKED);
         mScrimController.setRawPanelExpansionFraction(1.0f);
@@ -1660,6 +1730,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationAlpha_qsNotClipped_alphaMatchesNotificationExpansionProgress() {
         mScrimController.setClipsQsScrim(false);
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1695,6 +1766,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setNotificationsOverScrollAmount_setsTranslationYOnNotificationsScrim() {
         int overScrollAmount = 10;
 
@@ -1704,6 +1776,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnBehindScrim() {
         int overScrollAmount = 10;
 
@@ -1713,6 +1786,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnFrontScrim() {
         int overScrollAmount = 10;
 
@@ -1722,6 +1796,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationBoundsTopGetsPassedToKeyguard() {
         mScrimController.legacyTransitionTo(SHADE_LOCKED);
         mScrimController.setQsPosition(1f, 0);
@@ -1732,6 +1807,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
         mScrimController.setKeyguardOccluded(true);
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1742,6 +1818,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void transitionToDreaming() {
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -1761,6 +1838,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void keyguardGoingAwayUpdateScrims() {
         when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
         mScrimController.updateScrims();
@@ -1770,6 +1848,7 @@
 
 
     @Test
+    @DisableSceneContainer
     public void setUnOccludingAnimationKeyguard() {
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
@@ -1784,6 +1863,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testHidesScrimFlickerInActivity() {
         mScrimController.setKeyguardOccluded(true);
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1802,6 +1882,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void notificationAlpha_inKeyguardState_bouncerNotActive_clipsQsScrimFalse() {
         mScrimController.setClipsQsScrim(false);
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
@@ -1811,6 +1892,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void aodStateSetsFrontScrimToNotBlend() {
         mScrimController.legacyTransitionTo(ScrimState.AOD);
         assertFalse("Front scrim should not blend with main color",
@@ -1818,6 +1900,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void applyState_unlocked_bouncerShowing() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         mScrimController.setBouncerHiddenFraction(0.99f);
@@ -1827,6 +1910,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
         mScrimController.mBouncerToGoneTransition.accept(
@@ -1839,6 +1923,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
         when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
         mScrimController.mBouncerToGoneTransition.accept(
@@ -1849,6 +1934,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void primaryBouncerToGoneOnFinishCallsLightBarController() {
         reset(mLightBarController);
         mScrimController.mBouncerToGoneTransition.accept(
@@ -1860,6 +1946,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testDoNotAnimateChangeIfOccludeAnimationPlaying() {
         mScrimController.setOccludeAnimationPlaying(true);
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
@@ -1868,6 +1955,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
         mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
         when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 2f30b74..3190d3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -91,11 +91,11 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -122,7 +122,6 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
 
-    private static final int DISPLAY_ID = 0;
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
@@ -233,7 +232,6 @@
         mNotificationActivityStarter =
                 new StatusBarNotificationActivityStarter(
                         getContext(),
-                        DISPLAY_ID,
                         mHandler,
                         mUiBgExecutor,
                         mVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index fcdda9f..9da8e80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import android.service.dream.dreamManager
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
@@ -44,5 +45,6 @@
             deviceEntryInteractor = deviceEntryInteractor,
             wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
             dreamManager = dreamManager,
+            communalSettingsInteractor = communalSettingsInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index d995b86..0f7366d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -38,7 +38,6 @@
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
-            glanceableHubTransitions = glanceableHubTransitions,
             communalInteractor = communalInteractor,
             communalSceneInteractor = communalSceneInteractor,
             communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
index 494f08b..bf72e48 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -31,7 +31,6 @@
             scope = applicationCoroutineScope,
             mainDispatcher = testDispatcher,
             bgDispatcher = testDispatcher,
-            glanceableHubTransitions = glanceableHubTransitions,
             communalSettingsInteractor = communalSettingsInteractor,
             keyguardInteractor = keyguardInteractor,
             transitionRepository = keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index ff7a06c..985044c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -39,7 +39,6 @@
             keyguardInteractor = keyguardInteractor,
             shadeRepository = shadeRepository,
             powerInteractor = powerInteractor,
-            glanceableHubTransitions = glanceableHubTransitions,
             communalSettingsInteractor = communalSettingsInteractor,
             swipeToDismissInteractor = swipeToDismissInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
deleted file mode 100644
index a45b269..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.glanceableHubTransitions by
-    Kosmos.Fixture {
-        GlanceableHubTransitions(
-            transitionRepository = keyguardTransitionRepository,
-            transitionInteractor = keyguardTransitionInteractor,
-            communalInteractor = communalInteractor,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index b255b51..0443329 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -57,12 +57,10 @@
 
 var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
 var Kosmos.backgroundScope by Fixture { testScope.backgroundScope }
-var Kosmos.applicationCoroutineScope by Fixture { backgroundScope }
+var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
 var Kosmos.testCase: SysuiTestCase by Fixture()
-var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
-    backgroundScope.coroutineContext
-}
-var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture { testDispatcher }
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testDispatcher }
 
 /**
  * Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 446e106..60b371a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -90,6 +90,7 @@
 import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
 import com.android.systemui.util.time.systemClock
 import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
 
 /**
  * Helper for using [Kosmos] from Java.
@@ -192,4 +193,5 @@
     val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor }
     val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
     val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
+    val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index 5fc31f8..f287114 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -79,9 +80,9 @@
         with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles }
     }
 
-    override val tilesReadFromSetting: Channel<Pair<Set<TileSpec>, Int>> = Channel(capacity = 10)
+    override val tilesUpgradePath: Channel<Pair<TilesUpgradePath, Int>> = Channel(capacity = 10)
 
-    suspend fun sendTilesReadFromSetting(tiles: Set<TileSpec>, userId: Int) {
-        tilesReadFromSetting.send(tiles to userId)
+    suspend fun sendTilesFromUpgradePath(upgradePath: TilesUpgradePath, userId: Int) {
+        tilesUpgradePath.send(upgradePath to userId)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 5ff44e5..c5de02a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -26,7 +26,7 @@
     Kosmos.Fixture { fakeMinimumTilesRepository }
 
 var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
-val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+var Kosmos.defaultTilesRepository: DefaultTilesRepository by
     Kosmos.Fixture { fakeDefaultTilesRepository }
 
 val Kosmos.fakeTileSpecRepository by
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ConversationNotificationManagerKosmos.kt
similarity index 60%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ConversationNotificationManagerKosmos.kt
index 3a9525c..af53ae56 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ConversationNotificationManagerKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 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,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification
 
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
-    void opChanged(int op, int uid, String packageName, String persistentDeviceId);
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.conversationNotificationManager: ConversationNotificationManager by
+    Kosmos.Fixture { mock<ConversationNotificationManager>() }
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLoggerKosmos.kt
similarity index 61%
copy from core/java/com/android/internal/app/IAppOpsCallback.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLoggerKosmos.kt
index 3a9525c..cc40222 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLoggerKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2025 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,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.internal.app;
+package com.android.systemui.statusbar.notification.row
 
-// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
-oneway interface IAppOpsCallback {
-    void opChanged(int op, int uid, String packageName, String persistentDeviceId);
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notificationRowContentBinderLogger: NotificationRowContentBinderLogger by
+    Kosmos.Fixture { mock<NotificationRowContentBinderLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 0d6ac44..d787e2c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -52,7 +52,6 @@
     Kosmos.Fixture {
         StatusBarNotificationActivityStarter(
             applicationContext,
-            applicationContext.displayId,
             fakeExecutorHandler,
             fakeExecutor,
             notificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
index c6cf006..7dd0103 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelKosmos.kt
@@ -22,3 +22,10 @@
 
 val Kosmos.batteryViewModel by
     Kosmos.Fixture { BatteryViewModel(batteryInteractor, testableContext) }
+
+val Kosmos.batteryViewModelFactory by
+    Kosmos.Fixture {
+        object : BatteryViewModel.Factory {
+            override fun create(): BatteryViewModel = batteryViewModel
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index bc29dba..fbada93 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
 import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
+import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.batteryViewModelFactory
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarIconBlockListInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarInteractor
 
@@ -42,6 +43,7 @@
     Kosmos.Fixture {
         HomeStatusBarViewModelImpl(
             testableContext.displayId,
+            batteryViewModelFactory,
             tableLogBufferFactory,
             homeStatusBarInteractor,
             homeStatusBarIconBlockListInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 0d2aa4c..888b7e6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
 import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
 import com.android.systemui.volume.dialog.utils.volumeTracer
 
@@ -30,5 +31,6 @@
             volumeTracer,
             volumeDialogVisibilityRepository,
             volumeDialogController,
+            secureSettingsRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
index 7281e03..9699223 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
@@ -17,5 +17,16 @@
 package com.android.systemui.window.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.flow.MutableStateFlow
 
-val Kosmos.windowRootViewBlurRepository by Kosmos.Fixture { WindowRootViewBlurRepository() }
+val Kosmos.fakeWindowRootViewBlurRepository: FakeWindowRootViewBlurRepository by
+    Kosmos.Fixture { FakeWindowRootViewBlurRepository() }
+
+val Kosmos.windowRootViewBlurRepository: WindowRootViewBlurRepository by
+    Kosmos.Fixture { fakeWindowRootViewBlurRepository }
+
+class FakeWindowRootViewBlurRepository : WindowRootViewBlurRepository {
+    override val blurRadius: MutableStateFlow<Int> = MutableStateFlow(0)
+    override val isBlurOpaque: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isBlurSupported: MutableStateFlow<Boolean> = MutableStateFlow(false)
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index db8441d..5283df5 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -17,11 +17,16 @@
 package com.android.server.accessibility.autoclick;
 
 import static android.view.MotionEvent.BUTTON_PRIMARY;
+import static android.view.MotionEvent.BUTTON_SECONDARY;
 import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
 import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
 import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
 
 import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
 
 import android.accessibilityservice.AccessibilityTrace;
 import android.annotation.NonNull;
@@ -84,6 +89,23 @@
     @VisibleForTesting AutoclickTypePanel mAutoclickTypePanel;
     private WindowManager mWindowManager;
 
+    // Default click type is left-click.
+    private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+
+    @VisibleForTesting
+    final ClickPanelControllerInterface clickPanelController =
+            new ClickPanelControllerInterface() {
+                @Override
+                public void handleAutoclickTypeChange(@AutoclickType int clickType) {
+                    mActiveClickType = clickType;
+                }
+
+                @Override
+                public void toggleAutoclickPause() {
+                    // TODO(b/388872274): allows users to pause the autoclick.
+                }
+            };
+
     public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
         mTrace = trace;
         mContext = context;
@@ -124,7 +146,8 @@
         mAutoclickIndicatorView = new AutoclickIndicatorView(mContext);
 
         mWindowManager = mContext.getSystemService(WindowManager.class);
-        mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager);
+        mAutoclickTypePanel =
+                new AutoclickTypePanel(mContext, mWindowManager, clickPanelController);
 
         mAutoclickTypePanel.show();
         mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
@@ -644,6 +667,15 @@
 
             final long now = SystemClock.uptimeMillis();
 
+            // TODO(b/395094903): always triggers left-click when the cursor hovers over the
+            // autoclick type panel, to always allow users to change a different click type.
+            // Otherwise, if one chooses the right-click, this user won't be able to rely on
+            // autoclick to select other click types.
+            final int actionButton =
+                    mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
+                            ? BUTTON_SECONDARY
+                            : BUTTON_PRIMARY;
+
             MotionEvent downEvent =
                     MotionEvent.obtain(
                             /* downTime= */ now,
@@ -653,7 +685,7 @@
                             mTempPointerProperties,
                             mTempPointerCoords,
                             mMetaState,
-                            BUTTON_PRIMARY,
+                            actionButton,
                             /* xPrecision= */ 1.0f,
                             /* yPrecision= */ 1.0f,
                             mLastMotionEvent.getDeviceId(),
@@ -663,11 +695,11 @@
 
             MotionEvent pressEvent = MotionEvent.obtain(downEvent);
             pressEvent.setAction(MotionEvent.ACTION_BUTTON_PRESS);
-            pressEvent.setActionButton(BUTTON_PRIMARY);
+            pressEvent.setActionButton(actionButton);
 
             MotionEvent releaseEvent = MotionEvent.obtain(downEvent);
             releaseEvent.setAction(MotionEvent.ACTION_BUTTON_RELEASE);
-            releaseEvent.setActionButton(BUTTON_PRIMARY);
+            releaseEvent.setActionButton(actionButton);
             releaseEvent.setButtonState(0);
 
             MotionEvent upEvent = MotionEvent.obtain(downEvent);
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
index 557e1b2..01f359f 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
@@ -34,6 +34,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.R;
+
 // A visual indicator for the autoclick feature.
 public class AutoclickIndicatorView extends View {
     private static final String TAG = AutoclickIndicatorView.class.getSimpleName();
@@ -67,8 +69,7 @@
         super(context);
 
         mPaint = new Paint();
-        // TODO(b/383901288): update styling once determined by UX.
-        mPaint.setARGB(255, 52, 103, 235);
+        mPaint.setColor(context.getColor(R.color.materialColorPrimary));
         mPaint.setStyle(Paint.Style.STROKE);
         mPaint.setStrokeWidth(10);
 
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 2ef11f4..cf928e2 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
@@ -39,12 +40,40 @@
 
     private final String TAG = AutoclickTypePanel.class.getSimpleName();
 
+    public static final int AUTOCLICK_TYPE_LEFT_CLICK = 0;
+    public static final int AUTOCLICK_TYPE_RIGHT_CLICK = 1;
+    public static final int AUTOCLICK_TYPE_DOUBLE_CLICK = 2;
+    public static final int AUTOCLICK_TYPE_DRAG = 3;
+    public static final int AUTOCLICK_TYPE_SCROLL = 4;
+
+    // Types of click the AutoclickTypePanel supports.
+    @IntDef({
+        AUTOCLICK_TYPE_LEFT_CLICK,
+        AUTOCLICK_TYPE_RIGHT_CLICK,
+        AUTOCLICK_TYPE_DOUBLE_CLICK,
+        AUTOCLICK_TYPE_DRAG,
+        AUTOCLICK_TYPE_SCROLL,
+    })
+    public @interface AutoclickType {}
+
+    // An interface exposed to {@link AutoclickController) to handle different actions on the panel,
+    // including changing autoclick type, pausing/resuming autoclick.
+    public interface ClickPanelControllerInterface {
+        // Allows users to change a different autoclick type.
+        void handleAutoclickTypeChange(@AutoclickType int clickType);
+
+        // Allows users to pause/resume the autoclick.
+        void toggleAutoclickPause();
+    }
+
     private final Context mContext;
 
     private final View mContentView;
 
     private final WindowManager mWindowManager;
 
+    private final ClickPanelControllerInterface mClickPanelController;
+
     // Whether the panel is expanded or not.
     private boolean mExpanded = false;
 
@@ -56,9 +85,13 @@
 
     private LinearLayout mSelectedButton;
 
-    public AutoclickTypePanel(Context context, WindowManager windowManager) {
+    public AutoclickTypePanel(
+            Context context,
+            WindowManager windowManager,
+            ClickPanelControllerInterface clickPanelController) {
         mContext = context;
         mWindowManager = windowManager;
+        mClickPanelController = clickPanelController;
 
         mContentView =
                 LayoutInflater.from(context)
@@ -76,26 +109,35 @@
     }
 
     private void initializeButtonState() {
-        mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(mLeftClickButton));
-        mRightClickButton.setOnClickListener(v -> togglePanelExpansion(mRightClickButton));
-        mDoubleClickButton.setOnClickListener(v -> togglePanelExpansion(mDoubleClickButton));
-        mScrollButton.setOnClickListener(v -> togglePanelExpansion(mScrollButton));
-        mDragButton.setOnClickListener(v -> togglePanelExpansion(mDragButton));
+        mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_LEFT_CLICK));
+        mRightClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_RIGHT_CLICK));
+        mDoubleClickButton.setOnClickListener(
+                v -> togglePanelExpansion(AUTOCLICK_TYPE_DOUBLE_CLICK));
+        mScrollButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_SCROLL));
+        mDragButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_DRAG));
+
+        // TODO(b/388872274): registers listener for pause button and allows users to pause/resume
+        // the autoclick.
+        // TODO(b/388847771): registers listener for position button and allows users to move the
+        // panel to a different position.
 
         // Initializes panel as collapsed state and only displays the left click button.
         hideAllClickTypeButtons();
         mLeftClickButton.setVisibility(View.VISIBLE);
-        setSelectedButton(/* selectedButton= */ mLeftClickButton);
+        setSelectedClickType(AUTOCLICK_TYPE_LEFT_CLICK);
     }
 
     /** Sets the selected button and updates the newly and previously selected button styling. */
-    private void setSelectedButton(@NonNull LinearLayout selectedButton) {
+    private void setSelectedClickType(@AutoclickType int clickType) {
+        final LinearLayout selectedButton = getButtonFromClickType(clickType);
+
         // Updates the previously selected button styling.
         if (mSelectedButton != null) {
             toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false);
         }
 
         mSelectedButton = selectedButton;
+        mClickPanelController.handleAutoclickTypeChange(clickType);
 
         // Updates the newly selected button styling.
         toggleSelectedButtonStyle(selectedButton, /* isSelected= */ true);
@@ -130,7 +172,9 @@
     }
 
     /** Toggles the panel expanded or collapsed state. */
-    private void togglePanelExpansion(LinearLayout button) {
+    private void togglePanelExpansion(@AutoclickType int clickType) {
+        final LinearLayout button = getButtonFromClickType(clickType);
+
         if (mExpanded) {
             // If the panel is already in expanded state, we should collapse it by hiding all
             // buttons except the one user selected.
@@ -138,7 +182,7 @@
             button.setVisibility(View.VISIBLE);
 
             // Sets the newly selected button.
-            setSelectedButton(/* selectedButton= */ button);
+            setSelectedClickType(clickType);
         } else {
             // If the panel is already collapsed, we just need to expand it.
             showAllClickTypeButtons();
@@ -166,6 +210,17 @@
         mScrollButton.setVisibility(View.VISIBLE);
     }
 
+    private LinearLayout getButtonFromClickType(@AutoclickType int clickType) {
+        return switch (clickType) {
+            case AUTOCLICK_TYPE_LEFT_CLICK -> mLeftClickButton;
+            case AUTOCLICK_TYPE_RIGHT_CLICK -> mRightClickButton;
+            case AUTOCLICK_TYPE_DOUBLE_CLICK -> mDoubleClickButton;
+            case AUTOCLICK_TYPE_DRAG -> mDragButton;
+            case AUTOCLICK_TYPE_SCROLL -> mScrollButton;
+            default -> throw new IllegalArgumentException("Unknown clickType " + clickType);
+        };
+    }
+
     @VisibleForTesting
     boolean getExpansionStateForTesting() {
         return mExpanded;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 414db37..05301fd 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -588,10 +588,10 @@
         @Override
         @EnforcePermission(DELIVER_COMPANION_MESSAGES)
         public void attachSystemDataTransport(String packageName, int userId, int associationId,
-                ParcelFileDescriptor fd) {
+                                              ParcelFileDescriptor fd, int flags) {
             attachSystemDataTransport_enforcePermission();
 
-            mTransportManager.attachSystemDataTransport(associationId, fd);
+            mTransportManager.attachSystemDataTransport(associationId, fd, flags);
         }
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
index df3071e..42af059 100644
--- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -16,7 +16,9 @@
 
 package com.android.server.companion.securechannel;
 
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
 import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS;
 import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE;
 import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
 
@@ -34,15 +36,21 @@
 
 /**
  * Helper class to perform attestation verification synchronously.
+ *
+ * @hide
  */
 public class AttestationVerifier {
     private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
     private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
 
-    private final Context mContext;
+    private static final int EXTENDED_PATCH_LEVEL_DIFF_MONTHS = 24; // 2 years
 
-    AttestationVerifier(Context context) {
+    private final Context mContext;
+    private final int mFlags;
+
+    AttestationVerifier(Context context, int flags) {
         this.mContext = context;
+        this.mFlags = flags;
     }
 
     /**
@@ -59,10 +67,13 @@
             @NonNull byte[] remoteAttestation,
             @NonNull byte[] attestationChallenge
     ) throws SecureChannelException {
-        Bundle requirements = new Bundle();
+        final Bundle requirements = new Bundle();
         requirements.putByteArray(PARAM_CHALLENGE, attestationChallenge);
         requirements.putBoolean(PARAM_OWNED_BY_SYSTEM, true); // Custom parameter for CDM
 
+        // Apply flags to verifier requirements
+        updateRequirements(requirements);
+
         // Synchronously execute attestation verification.
         AtomicInteger verificationResult = new AtomicInteger(0);
         CountDownLatch verificationFinished = new CountDownLatch(1);
@@ -96,4 +107,15 @@
 
         return verificationResult.get();
     }
+
+    private void updateRequirements(Bundle requirements) {
+        if (mFlags == 0) {
+            return;
+        }
+
+        if ((mFlags & TRANSPORT_FLAG_EXTEND_PATCH_DIFF) > 0) {
+            requirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS,
+                    EXTENDED_PATCH_LEVEL_DIFF_MONTHS);
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 2d3782f..6c7c9b3 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -59,6 +59,7 @@
     private final Callback mCallback;
     private final byte[] mPreSharedKey;
     private final AttestationVerifier mVerifier;
+    private final int mFlags;
 
     private volatile boolean mStopped;
     private volatile boolean mInProgress;
@@ -89,7 +90,7 @@
             @NonNull Callback callback,
             @NonNull byte[] preSharedKey
     ) {
-        this(in, out, callback, preSharedKey, null);
+        this(in, out, callback, preSharedKey, null, 0);
     }
 
     /**
@@ -100,14 +101,16 @@
      * @param out output stream from which data is sent out
      * @param callback subscription to received messages from the channel
      * @param context context for fetching the Attestation Verifier Framework system service
+     * @param flags flags for custom security settings on the channel
      */
     public SecureChannel(
             @NonNull final InputStream in,
             @NonNull final OutputStream out,
             @NonNull Callback callback,
-            @NonNull Context context
+            @NonNull Context context,
+            int flags
     ) {
-        this(in, out, callback, null, new AttestationVerifier(context));
+        this(in, out, callback, null, new AttestationVerifier(context, flags), flags);
     }
 
     public SecureChannel(
@@ -115,13 +118,15 @@
             final OutputStream out,
             Callback callback,
             byte[] preSharedKey,
-            AttestationVerifier verifier
+            AttestationVerifier verifier,
+            int flags
     ) {
         this.mInput = in;
         this.mOutput = out;
         this.mCallback = callback;
         this.mPreSharedKey = preSharedKey;
         this.mVerifier = verifier;
+        this.mFlags = flags;
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3608360..92d9fb0 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,7 +16,11 @@
 
 package com.android.server.companion.transport;
 
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
+
+import static com.android.server.companion.transport.TransportUtils.enforceAssociationCanUseTransportFlags;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -152,10 +156,14 @@
     /**
      * Attach transport.
      */
-    public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
+    public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd,
+                                          int flags) {
         Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
 
-        mAssociationStore.getAssociationWithCallerChecks(associationId);
+        AssociationInfo association =
+                mAssociationStore.getAssociationWithCallerChecks(associationId);
+
+        enforceAssociationCanUseTransportFlags(association, flags);
 
         synchronized (mTransports) {
             if (mTransports.contains(associationId)) {
@@ -163,7 +171,7 @@
             }
 
             // TODO: Implement new API to pass a PSK
-            initializeTransport(associationId, fd, null);
+            initializeTransport(association, fd, null, flags);
 
             notifyOnTransportsChanged();
         }
@@ -217,10 +225,12 @@
         }
     }
 
-    private void initializeTransport(int associationId,
+    private void initializeTransport(AssociationInfo association,
                                      ParcelFileDescriptor fd,
-                                     byte[] preSharedKey) {
+                                     byte[] preSharedKey,
+                                     int flags) {
         Slog.i(TAG, "Initializing transport");
+        int associationId = association.getId();
         Transport transport;
         if (!isSecureTransportEnabled()) {
             // If secure transport is explicitly disabled for testing, use raw transport
@@ -230,15 +240,21 @@
             // If device is debug build, use hardcoded test key for authentication
             Slog.d(TAG, "Creating an unauthenticated secure channel");
             final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8);
-            transport = new SecureTransport(associationId, fd, mContext, testKey, null);
+            transport = new SecureTransport(associationId, fd, mContext, testKey, null, 0);
         } else if (preSharedKey != null) {
             // If either device is not Android, then use app-specific pre-shared key
             Slog.d(TAG, "Creating a PSK-authenticated secure channel");
-            transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null);
+            transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null, 0);
+        } else if (DEVICE_PROFILE_WEARABLE_SENSING.equals(association.getDeviceProfile())) {
+            // If device is glasses with WEARABLE_SENSING profile, extend the allowed patch
+            // difference to 2 years instead of 1.
+            Slog.d(TAG, "Creating a secure channel with extended patch difference allowance");
+            transport = new SecureTransport(associationId, fd, mContext,
+                    TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
         } else {
             // If none of the above applies, then use secure channel with attestation verification
             Slog.d(TAG, "Creating a secure channel");
-            transport = new SecureTransport(associationId, fd, mContext);
+            transport = new SecureTransport(associationId, fd, mContext, flags);
         }
 
         addMessageListenersToTransport(transport);
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 1e95e65..77dc809 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -36,15 +36,22 @@
 
     private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
 
-    SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+    SecureTransport(int associationId, ParcelFileDescriptor fd, Context context, int flags) {
         super(associationId, fd, context);
-        mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context);
+        mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context, flags);
     }
 
     SecureTransport(int associationId, ParcelFileDescriptor fd, Context context,
-            byte[] preSharedKey, AttestationVerifier verifier) {
+            byte[] preSharedKey, AttestationVerifier verifier, int flags) {
         super(associationId, fd, context);
-        mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, preSharedKey, verifier);
+        mSecureChannel = new SecureChannel(
+                mRemoteIn,
+                mRemoteOut,
+                this,
+                preSharedKey,
+                verifier,
+                flags
+        );
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/transport/TransportUtils.java b/services/companion/java/com/android/server/companion/transport/TransportUtils.java
new file mode 100644
index 0000000..7a15c11
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/TransportUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.transport;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
+import static android.companion.CompanionDeviceManager.TRANSPORT_FLAG_EXTEND_PATCH_DIFF;
+
+import static java.util.Collections.unmodifiableMap;
+
+import android.companion.AssociationInfo;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Utility class for transport manager.
+ * @hide
+ */
+public final class TransportUtils {
+
+    /**
+     * Device profile -> Union of allowlisted transport flags
+     */
+    private static final Map<String, Integer> DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST;
+    static {
+        final Map<String, Integer> map = new ArrayMap<>();
+        map.put(DEVICE_PROFILE_WEARABLE_SENSING,
+                TRANSPORT_FLAG_EXTEND_PATCH_DIFF);
+        DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST = unmodifiableMap(map);
+    }
+
+    /**
+     * Enforce that the association that is trying to attach a transport with provided flags has
+     * one of the allowlisted device profiles that may apply the flagged features.
+     *
+     * @param association Association for which transport is being attached
+     * @param flags Flags for features being applied to the transport
+     */
+    public static void enforceAssociationCanUseTransportFlags(
+            AssociationInfo association, int flags) {
+        if (flags == 0) {
+            return;
+        }
+
+        final String deviceProfile = association.getDeviceProfile();
+        if (!DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST.containsKey(deviceProfile)) {
+            throw new IllegalArgumentException("Association (id=" + association.getId()
+                    + ") with device profile " + deviceProfile + " does not support the "
+                    + "usage of transport flags.");
+        }
+
+        int allowedFlags = DEVICE_PROFILE_TRANSPORT_FLAGS_ALLOWLIST.get(deviceProfile);
+
+        // Ensure that every non-zero bits in flags are also present in allowed flags
+        if ((allowedFlags & flags) != flags) {
+            throw new IllegalArgumentException("Association (id=" + association.getId()
+                    + ") does not have the device profile required to use at least "
+                    + "one of the flags in this transport.");
+        }
+    }
+
+    private TransportUtils() {}
+}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index c633830..f1007e7 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -186,9 +186,28 @@
         "core_libraries",
         "crumpet",
         "dck_framework",
+        "desktop_apps",
+        "desktop_better_together",
+        "desktop_bsp",
+        "desktop_camera",
         "desktop_connectivity",
+        "desktop_display",
+        "desktop_commercial",
+        "desktop_firmware",
+        "desktop_graphics",
         "desktop_hwsec",
+        "desktop_input",
+        "desktop_kernel",
+        "desktop_ml",
+        "desktop_serviceability",
+        "desktop_oobe",
+        "desktop_peripherals",
+        "desktop_pnp",
+        "desktop_security",
         "desktop_stats",
+        "desktop_sysui",
+        "desktop_users_and_accounts",
+        "desktop_video",
         "desktop_wifi",
         "devoptions_settings",
         "game",
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index c0a97db..7672011 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -361,6 +361,14 @@
                 case POPULATE_GAME_MODE_SETTINGS: {
                     removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
                     final int userId = (int) msg.obj;
+                    synchronized (mLock) {
+                        if (!mSettings.containsKey(userId)) {
+                            GameManagerSettings userSettings = new GameManagerSettings(
+                                    Environment.getDataSystemDeDirectory(userId));
+                            mSettings.put(userId, userSettings);
+                            userSettings.readPersistentDataLocked();
+                        }
+                    }
                     final String[] packageNames = getInstalledGamePackageNames(userId);
                     updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
                     break;
@@ -990,8 +998,7 @@
         @Override
         public void onUserStarting(@NonNull TargetUser user) {
             Slog.d(TAG, "Starting user " + user.getUserIdentifier());
-            mService.onUserStarting(user,
-                    Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
+            mService.onUserStarting(user, /*settingDataDirOverride*/ null);
         }
 
         @Override
@@ -1596,13 +1603,16 @@
         }
     }
 
-    void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
+    void onUserStarting(@NonNull TargetUser user, File settingDataDirOverride) {
         final int userId = user.getUserIdentifier();
-        synchronized (mLock) {
-            if (!mSettings.containsKey(userId)) {
-                GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
-                mSettings.put(userId, userSettings);
-                userSettings.readPersistentDataLocked();
+        if (settingDataDirOverride != null) {
+            synchronized (mLock) {
+                if (!mSettings.containsKey(userId)) {
+                    GameManagerSettings userSettings = new GameManagerSettings(
+                            settingDataDirOverride);
+                    mSettings.put(userId, userSettings);
+                    userSettings.readPersistentDataLocked();
+                }
             }
         }
         sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING,
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index c57a1f7..fd4bf2f 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -221,9 +221,7 @@
             return false;
         }
 
-        try {
-            final FileInputStream str = mSettingsFile.openRead();
-
+        try (FileInputStream str = mSettingsFile.openRead()) {
             final TypedXmlPullParser parser = Xml.resolvePullParser(str);
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -251,7 +249,6 @@
                             + type);
                 }
             }
-            str.close();
         } catch (XmlPullParserException | java.io.IOException e) {
             Slog.wtf(TAG, "Error reading game manager settings", e);
             return false;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6f79f70..86871ea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -12598,7 +12598,7 @@
     //==========================================================================================
     static final int LOG_NB_EVENTS_LIFECYCLE = 20;
     static final int LOG_NB_EVENTS_PHONE_STATE = 20;
-    static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 50;
+    static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 200;
     static final int LOG_NB_EVENTS_FORCE_USE = 20;
     static final int LOG_NB_EVENTS_VOLUME = 100;
     static final int LOG_NB_EVENTS_DYN_POLICY = 10;
@@ -15082,11 +15082,13 @@
         final String key = "additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
-        long delayMillis;
-        try {
-            delayMillis = Long.parseLong(reply.substring(key.length() + 1));
-        } catch (NullPointerException e) {
-            delayMillis = 0;
+        long delayMillis = 0;
+        if (reply.contains(key)) {
+            try {
+                delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+            } catch (NullPointerException e) {
+                delayMillis = 0;
+            }
         }
         return delayMillis;
     }
@@ -15112,11 +15114,13 @@
         final String key = "max_additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
-        long delayMillis;
-        try {
-            delayMillis = Long.parseLong(reply.substring(key.length() + 1));
-        } catch (NullPointerException e) {
-            delayMillis = 0;
+        long delayMillis = 0;
+        if (reply.contains(key)) {
+            try {
+                delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+            } catch (NullPointerException e) {
+                delayMillis = 0;
+            }
         }
         return delayMillis;
     }
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index a1e8f08..aab2760 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -122,11 +122,6 @@
             Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE,
             Flags::alwaysRotateDisplayDevice);
 
-    private final FlagState mRefreshRateVotingTelemetry = new FlagState(
-            Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
-            Flags::refreshRateVotingTelemetry
-    );
-
     private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
             Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
             Flags::enablePixelAnisotropyCorrection
@@ -403,10 +398,6 @@
         return mAlwaysRotateDisplayDevice.isEnabled();
     }
 
-    public boolean isRefreshRateVotingTelemetryEnabled() {
-        return mRefreshRateVotingTelemetry.isEnabled();
-    }
-
     public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
         return mPixelAnisotropyCorrectionEnabled.isEnabled();
     }
@@ -626,7 +617,6 @@
         pw.println(" " + mAutoBrightnessModesFlagState);
         pw.println(" " + mFastHdrTransitions);
         pw.println(" " + mAlwaysRotateDisplayDevice);
-        pw.println(" " + mRefreshRateVotingTelemetry);
         pw.println(" " + mPixelAnisotropyCorrectionEnabled);
         pw.println(" " + mSensorBasedBrightnessThrottling);
         pw.println(" " + mIdleScreenRefreshRateTimeout);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index cc0bbde..8211feb 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -191,14 +191,6 @@
 }
 
 flag {
-    name: "refresh_rate_voting_telemetry"
-    namespace: "display_manager"
-    description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
-    bug: "310029108"
-    is_fixed_read_only: true
-}
-
-flag {
     name: "enable_pixel_anisotropy_correction"
     namespace: "display_manager"
     description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 1dd4a9b..c37733b 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -229,8 +229,7 @@
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
-        mVotesStatsReporter = injector.getVotesStatsReporter(
-                displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
+        mVotesStatsReporter = injector.getVotesStatsReporter();
         mSupportedModesByDisplay = new SparseArray<>();
         mAppSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
@@ -3141,7 +3140,7 @@
         SensorManagerInternal getSensorManagerInternal();
 
         @Nullable
-        VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled);
+        VotesStatsReporter getVotesStatsReporter();
     }
 
     @VisibleForTesting
@@ -3281,10 +3280,9 @@
         }
 
         @Override
-        public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+        public VotesStatsReporter getVotesStatsReporter() {
             // if frame rate override supported, renderRates will be ignored in mode selection
-            return new VotesStatsReporter(supportsFrameRateOverride(),
-                    refreshRateVotingTelemetryEnabled);
+            return new VotesStatsReporter(supportsFrameRateOverride());
         }
 
         private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index 7562a52..7b579c0 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.os.Trace;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 
 import com.android.internal.util.FrameworkStatsLog;
@@ -36,13 +37,11 @@
     private static final String TAG = "VotesStatsReporter";
     private static final int REFRESH_RATE_NOT_LIMITED = 1000;
     private final boolean mIgnoredRenderRate;
-    private final boolean mFrameworkStatsLogReportingEnabled;
 
-    private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1;
+    private final SparseIntArray mLastMinPriorityByDisplay = new SparseIntArray();
 
-    public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
+    VotesStatsReporter(boolean ignoreRenderRate) {
         mIgnoredRenderRate = ignoreRenderRate;
-        mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
     }
 
     void reportVoteChanged(int displayId, int priority,  @Nullable Vote vote) {
@@ -57,32 +56,27 @@
         int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
         Trace.traceCounter(Trace.TRACE_TAG_POWER,
                 TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
-        if (mFrameworkStatsLogReportingEnabled) {
-            FrameworkStatsLog.write(
-                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
-                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
-                    maxRefreshRate, -1);
-        }
+        FrameworkStatsLog.write(
+                DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+                DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+                maxRefreshRate, -1);
     }
 
     private void reportVoteRemoved(int displayId, int priority) {
         Trace.traceCounter(Trace.TRACE_TAG_POWER,
                 TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
-        if (mFrameworkStatsLogReportingEnabled) {
-            FrameworkStatsLog.write(
-                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
-                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
-        }
+        FrameworkStatsLog.write(
+                DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+                DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
     }
 
     void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
             SparseArray<Vote> votes) {
-        if (!mFrameworkStatsLogReportingEnabled) {
-            return;
-        }
+        int lastMinPriorityReported = mLastMinPriorityByDisplay.get(
+                displayId, Vote.MAX_PRIORITY + 1);
         int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
         for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) {
-            if (priority < mLastMinPriorityReported && priority < minPriority) {
+            if (priority < lastMinPriorityReported && priority < minPriority) {
                 continue;
             }
             Vote vote = votes.get(priority);
@@ -91,7 +85,7 @@
             }
 
             // Was previously reported ACTIVE, changed to ADDED
-            if (priority >= mLastMinPriorityReported && priority < minPriority) {
+            if (priority >= lastMinPriorityReported && priority < minPriority) {
                 int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
                 FrameworkStatsLog.write(
                         DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
@@ -99,7 +93,7 @@
                         maxRefreshRate, selectedRefreshRate);
             }
             // Was previously reported ADDED, changed to ACTIVE
-            if (priority >= minPriority && priority < mLastMinPriorityReported) {
+            if (priority >= minPriority && priority < lastMinPriorityReported) {
                 int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
                 FrameworkStatsLog.write(
                         DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
@@ -107,7 +101,7 @@
                         maxRefreshRate, selectedRefreshRate);
             }
 
-            mLastMinPriorityReported = minPriority;
+            mLastMinPriorityByDisplay.put(displayId, minPriority);
         }
     }
 
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 4505d0e..7e5c1bc 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -86,3 +86,10 @@
     description: "Enable the time notifications feature, a toggle to enable/disable time-related notifications in Date & Time Settings"
     bug: "283267917"
 }
+
+flag {
+    name: "certpininstaller_removal"
+    namespace: "network_security"
+    description: "Remove CertPinInstallReceiver from the platform"
+    bug: "391205997"
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 108afba..977c029 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -256,13 +256,11 @@
             ));
         }
         if (keyboardA11yShortcutControl()) {
-            if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
-                systemShortcuts.add(createKeyGesture(
-                        KeyEvent.KEYCODE_3,
-                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
-                ));
-            }
+            systemShortcuts.add(createKeyGesture(
+                    KeyEvent.KEYCODE_3,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+            ));
             if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
                 systemShortcuts.add(createKeyGesture(
                         KeyEvent.KEYCODE_4,
@@ -270,20 +268,16 @@
                         KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
                 ));
             }
-            if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
-                systemShortcuts.add(createKeyGesture(
-                        KeyEvent.KEYCODE_5,
-                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
-                ));
-            }
-            if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
-                systemShortcuts.add(createKeyGesture(
-                        KeyEvent.KEYCODE_6,
-                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
-                ));
-            }
+            systemShortcuts.add(createKeyGesture(
+                    KeyEvent.KEYCODE_5,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+            ));
+            systemShortcuts.add(createKeyGesture(
+                    KeyEvent.KEYCODE_6,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+            ));
         }
         synchronized (mGestureLock) {
             for (InputGestureData systemShortcut : systemShortcuts) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4a5f4a1..8624f42 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2775,7 +2775,7 @@
                 }
                 return true;
             case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS:
-                if (complete && InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+                if (complete) {
                     final boolean bounceKeysEnabled =
                             InputSettings.isAccessibilityBounceKeysEnabled(mContext);
                     InputSettings.setAccessibilityBounceKeysThreshold(mContext,
@@ -2793,7 +2793,7 @@
                 }
                 break;
             case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS:
-                if (complete && InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+                if (complete) {
                     final boolean stickyKeysEnabled =
                             InputSettings.isAccessibilityStickyKeysEnabled(mContext);
                     InputSettings.setAccessibilityStickyKeysEnabled(mContext, !stickyKeysEnabled);
@@ -2801,7 +2801,7 @@
                 }
                 break;
             case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
-                if (complete && InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+                if (complete) {
                     final boolean slowKeysEnabled =
                             InputSettings.isAccessibilitySlowKeysEnabled(mContext);
                     InputSettings.setAccessibilitySlowKeysThreshold(mContext,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index ddace17..2ea6117 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -43,7 +43,6 @@
 
 import java.util.Collection;
 import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * A class that represents a broker for the endpoint registered by the client app. This class
@@ -89,8 +88,11 @@
     /** The remote callback interface for this endpoint. */
     private final IContextHubEndpointCallback mContextHubEndpointCallback;
 
-    /** True if this endpoint is registered with the service. */
-    private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
+    /** True if this endpoint is registered with the service/HAL. */
+    @GuardedBy("mRegistrationLock")
+    private boolean mIsRegistered = false;
+
+    private final Object mRegistrationLock = new Object();
 
     private final Object mOpenSessionLock = new Object();
 
@@ -192,7 +194,7 @@
     public int openSession(HubEndpointInfo destination, String serviceDescriptor)
             throws RemoteException {
         super.openSession_enforcePermission();
-        if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+        if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
         if (!hasEndpointPermissions(destination)) {
             throw new SecurityException(
                     "Insufficient permission to open a session with endpoint: " + destination);
@@ -223,7 +225,7 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public void closeSession(int sessionId, int reason) throws RemoteException {
         super.closeSession_enforcePermission();
-        if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+        if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
         if (!cleanupSessionResources(sessionId)) {
             throw new IllegalArgumentException(
                     "Unknown session ID in closeSession: id=" + sessionId);
@@ -235,19 +237,26 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public void unregister() {
         super.unregister_enforcePermission();
-        mIsRegistered.set(false);
-        try {
-            mHubInterface.unregisterEndpoint(mHalEndpointInfo);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
-        }
         synchronized (mOpenSessionLock) {
             // Iterate in reverse since cleanupSessionResources will remove the entry
             for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
                 int id = mSessionInfoMap.keyAt(i);
+                halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
                 cleanupSessionResources(id);
             }
         }
+        synchronized (mRegistrationLock) {
+            if (!isRegistered()) {
+                Log.w(TAG, "Attempting to unregister when already unregistered");
+                return;
+            }
+            mIsRegistered = false;
+            try {
+                mHubInterface.unregisterEndpoint(mHalEndpointInfo);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
+            }
+        }
         mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
         releaseWakeLockOnExit();
     }
@@ -335,7 +344,7 @@
     /** Invoked when the underlying binder of this broker has died at the client process. */
     @Override
     public void binderDied() {
-        if (mIsRegistered.get()) {
+        if (isRegistered()) {
             unregister();
         }
     }
@@ -365,6 +374,22 @@
         }
     }
 
+    /**
+     * Registers this endpoints with the Context Hub HAL.
+     *
+     * @throws RemoteException if the registrations fails with a RemoteException
+     */
+    /* package */ void register() throws RemoteException {
+        synchronized (mRegistrationLock) {
+            if (isRegistered()) {
+                Log.w(TAG, "Attempting to register when already registered");
+            } else {
+                mHubInterface.registerEndpoint(mHalEndpointInfo);
+                mIsRegistered = true;
+            }
+        }
+    }
+
     /* package */ void attachDeathRecipient() throws RemoteException {
         if (mContextHubEndpointCallback != null) {
             mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
@@ -425,6 +450,24 @@
         }
     }
 
+    /* package */ void onHalRestart() {
+        synchronized (mRegistrationLock) {
+            mIsRegistered = false;
+            try {
+                register();
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
+            }
+        }
+        synchronized (mOpenSessionLock) {
+            for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
+                int id = mSessionInfoMap.keyAt(i);
+                onCloseEndpointSession(id, Reason.HUB_RESET);
+            }
+        }
+        // TODO(b/390029594): Cancel any ongoing reliable communication transactions
+    }
+
     private Optional<Byte> onEndpointSessionOpenRequestInternal(
             int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
         if (!hasEndpointPermissions(initiator)) {
@@ -465,24 +508,21 @@
             }
             remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo();
         }
-        if (!ContextHubServiceUtil.notePermissions(
-                mAppOpsManager,
-                mUid,
-                mPackageName,
-                mAttributionTag,
-                remote.getRequiredPermissions(),
-                RECEIVE_MSG_NOTE
-                        + "-0x"
-                        + Long.toHexString(remote.getIdentifier().getHub())
-                        + "-0x"
-                        + Long.toHexString(remote.getIdentifier().getEndpoint()))) {
-            Log.e(
-                    TAG,
-                    "Dropping message from "
-                            + remote
-                            + ". "
-                            + mPackageName
-                            + " doesn't have permission");
+
+        try {
+            Binder.withCleanCallingIdentity(
+                    () -> {
+                        if (!notePermissions(remote)) {
+                            throw new RuntimeException(
+                                    "Dropping message from "
+                                            + remote
+                                            + ". "
+                                            + mPackageName
+                                            + " doesn't have permission");
+                        }
+                    });
+        } catch (RuntimeException e) {
+            Log.e(TAG, e.getMessage());
             return ErrorCode.PERMISSION_DENIED;
         }
 
@@ -553,7 +593,7 @@
     private void acquireWakeLock() {
         Binder.withCleanCallingIdentity(
                 () -> {
-                    if (mIsRegistered.get()) {
+                    if (isRegistered()) {
                         mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
                     }
                 });
@@ -608,4 +648,30 @@
         }
         return true;
     }
+
+    private boolean isRegistered() {
+        synchronized (mRegistrationLock) {
+            return mIsRegistered;
+        }
+    }
+
+    /**
+     * Utility to call notePermissions for e.g. when processing a message from a given endpoint for
+     * this broker.
+     *
+     * @param endpoint The endpoint to check permissions for this broker.
+     */
+    private boolean notePermissions(HubEndpointInfo endpoint) {
+        return ContextHubServiceUtil.notePermissions(
+                mAppOpsManager,
+                mUid,
+                mPackageName,
+                mAttributionTag,
+                endpoint.getRequiredPermissions(),
+                RECEIVE_MSG_NOTE
+                        + "-0x"
+                        + Long.toHexString(endpoint.getIdentifier().getHub())
+                        + "-0x"
+                        + Long.toHexString(endpoint.getIdentifier().getEndpoint()));
+    }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index ed98bf9..06aeb62 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -206,12 +206,6 @@
         EndpointInfo halEndpointInfo =
                 ContextHubServiceUtil.createHalEndpointInfo(
                         pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
-        try {
-            mHubInterface.registerEndpoint(halEndpointInfo);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
-            throw e;
-        }
         broker =
                 new ContextHubEndpointBroker(
                         mContext,
@@ -222,6 +216,7 @@
                         packageName,
                         attributionTag,
                         mTransactionManager);
+        broker.register();
         mEndpointMap.put(endpointId, broker);
 
         try {
@@ -282,6 +277,14 @@
         mEndpointMap.remove(endpointId);
     }
 
+    /** Invoked by the service when the Context Hub HAL restarts. */
+    /* package */ void onHalRestart() {
+        for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+            // The broker will close existing sessions and re-register itself
+            broker.onHalRestart();
+        }
+    }
+
     @Override
     public void onEndpointSessionOpenRequest(
             int sessionId,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
index 88764b6..a3d9429 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -21,9 +21,6 @@
 import android.hardware.contexthub.IEndpointCallback;
 import android.hardware.contexthub.Message;
 import android.hardware.contexthub.MessageDeliveryStatus;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
 import android.os.RemoteException;
 
 /** IEndpointCallback implementation. */
@@ -32,11 +29,6 @@
     private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
     private final IEndpointSessionCallback mEndpointSessionCallback;
 
-    // Use this thread in case where the execution requires to be on an async service thread.
-    private final HandlerThread mHandlerThread =
-            new HandlerThread("Context Hub endpoint callback", Process.THREAD_PRIORITY_BACKGROUND);
-    private Handler mHandler;
-
     /** Interface for listening for endpoint start and stop events. */
     public interface IEndpointLifecycleCallback {
         /** Called when a batch of endpoints started. */
@@ -73,9 +65,6 @@
             IEndpointSessionCallback endpointSessionCallback) {
         mEndpointLifecycleCallback = endpointLifecycleCallback;
         mEndpointSessionCallback = endpointSessionCallback;
-
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
     }
 
     @Override
@@ -88,7 +77,7 @@
         for (int i = 0; i < halEndpointInfos.length; i++) {
             endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]);
         }
-        mHandler.post(() -> mEndpointLifecycleCallback.onEndpointStarted(endpointInfos));
+        mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
     }
 
     @Override
@@ -98,7 +87,7 @@
         for (int i = 0; i < halEndpointIds.length; i++) {
             endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
         }
-        mHandler.post(() -> mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason));
+        mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
     }
 
     @Override
@@ -109,37 +98,33 @@
                 new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id);
         HubEndpointInfo.HubEndpointIdentifier initiatorId =
                 new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id);
-        mHandler.post(
-                () ->
-                        mEndpointSessionCallback.onEndpointSessionOpenRequest(
-                                sessionId, destinationId, initiatorId, serviceDescriptor));
+        mEndpointSessionCallback.onEndpointSessionOpenRequest(
+                sessionId, destinationId, initiatorId, serviceDescriptor);
     }
 
     @Override
     public void onCloseEndpointSession(int sessionId, byte reason) throws RemoteException {
-        mHandler.post(() -> mEndpointSessionCallback.onCloseEndpointSession(sessionId, reason));
+        mEndpointSessionCallback.onCloseEndpointSession(sessionId, reason);
     }
 
     @Override
     public void onEndpointSessionOpenComplete(int sessionId) throws RemoteException {
-        mHandler.post(() -> mEndpointSessionCallback.onEndpointSessionOpenComplete(sessionId));
+        mEndpointSessionCallback.onEndpointSessionOpenComplete(sessionId);
     }
 
     @Override
     public void onMessageReceived(int sessionId, Message message) throws RemoteException {
         HubMessage hubMessage = ContextHubServiceUtil.createHubMessage(message);
-        mHandler.post(() -> mEndpointSessionCallback.onMessageReceived(sessionId, hubMessage));
+        mEndpointSessionCallback.onMessageReceived(sessionId, hubMessage);
     }
 
     @Override
     public void onMessageDeliveryStatusReceived(
             int sessionId, MessageDeliveryStatus messageDeliveryStatus) throws RemoteException {
-        mHandler.post(
-                () ->
-                        mEndpointSessionCallback.onMessageDeliveryStatusReceived(
-                                sessionId,
-                                messageDeliveryStatus.messageSequenceNumber,
-                                messageDeliveryStatus.errorCode));
+        mEndpointSessionCallback.onMessageDeliveryStatusReceived(
+                sessionId,
+                messageDeliveryStatus.messageSequenceNumber,
+                messageDeliveryStatus.errorCode);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2b0ca14..502a7ae 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -259,6 +259,9 @@
             if (mHubInfoRegistry != null) {
                 mHubInfoRegistry.onHalRestart();
             }
+            if (mEndpointManager != null) {
+                mEndpointManager.onHalRestart();
+            }
             resetSettings();
             if (Flags.reconnectHostEndpointsAfterHalRestart()) {
                 mClientManager.forEachClientOfHub(mContextHubId,
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index e2889fa..18bccd8 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -91,7 +91,7 @@
                 updateAttributes = true;
             }
             if (restrictAudioAttributesAlarm()
-                    && record.getNotification().category != CATEGORY_ALARM
+                    && !CATEGORY_ALARM.equals(record.getNotification().category)
                     && attributes.getUsage() == AudioAttributes.USAGE_ALARM) {
                 updateAttributes = true;
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0aaa0fe..76c5240 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3733,8 +3733,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_3:
-                if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
-                        && keyboardA11yShortcutControl()) {
+                if (keyboardA11yShortcutControl()) {
                     if (firstDown && event.isMetaPressed()
                             && event.isAltPressed()) {
                         final boolean bounceKeysEnabled =
@@ -3765,8 +3764,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_5:
-                if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
-                        && keyboardA11yShortcutControl()) {
+                if (keyboardA11yShortcutControl()) {
                     if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
                         final boolean stickyKeysEnabled =
                                 InputSettings.isAccessibilityStickyKeysEnabled(
@@ -3780,8 +3778,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_6:
-                if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
-                        && keyboardA11yShortcutControl()) {
+                if (keyboardA11yShortcutControl()) {
                     if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
                         final boolean slowKeysEnabled =
                                 InputSettings.isAccessibilitySlowKeysEnabled(mContext);
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index dc1f936..f060e4d 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -17,8 +17,8 @@
 package com.android.server.security;
 
 import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_BOOT_STATE;
-import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
 import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS;
 import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS;
 import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF;
 import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN;
@@ -47,12 +47,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
 
-import org.json.JSONObject;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
@@ -60,7 +56,6 @@
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
-import java.security.cert.PKIXCertPathChecker;
 import java.security.cert.PKIXParameters;
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509Certificate;
@@ -69,7 +64,6 @@
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -126,6 +120,7 @@
     private final LocalDate mTestLocalPatchDate;
     private final CertificateFactory mCertificateFactory;
     private final CertPathValidator mCertPathValidator;
+    private final CertificateRevocationStatusManager mCertificateRevocationStatusManager;
     private final DumpLogger mDumpLogger;
 
     AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
@@ -135,6 +130,7 @@
         mCertificateFactory = CertificateFactory.getInstance("X.509");
         mCertPathValidator = CertPathValidator.getInstance("PKIX");
         mTrustAnchors = getTrustAnchors();
+        mCertificateRevocationStatusManager = new CertificateRevocationStatusManager(mContext);
         mRevocationEnabled = true;
         mTestSystemDate = null;
         mTestLocalPatchDate = null;
@@ -150,6 +146,7 @@
         mCertificateFactory = CertificateFactory.getInstance("X.509");
         mCertPathValidator = CertPathValidator.getInstance("PKIX");
         mTrustAnchors = trustAnchors;
+        mCertificateRevocationStatusManager = new CertificateRevocationStatusManager(mContext);
         mRevocationEnabled = revocationEnabled;
         mTestSystemDate = systemDate;
         mTestLocalPatchDate = localPatchDate;
@@ -300,15 +297,14 @@
 
         CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
         PKIXParameters validationParams = new PKIXParameters(mTrustAnchors);
-        if (mRevocationEnabled) {
-            // Checks Revocation Status List based on
-            // https://developer.android.com/training/articles/security-key-attestation#certificate_status
-            PKIXCertPathChecker checker = new AndroidRevocationStatusListChecker();
-            validationParams.addCertPathChecker(checker);
-        }
         // Do not use built-in revocation status checker.
         validationParams.setRevocationEnabled(false);
         mCertPathValidator.validate(certificatePath, validationParams);
+        if (mRevocationEnabled) {
+            // Checks Revocation Status List based on
+            // https://developer.android.com/training/articles/security-key-attestation#certificate_status
+            mCertificateRevocationStatusManager.checkRevocationStatus(certificates);
+        }
     }
 
     private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
@@ -574,96 +570,6 @@
                 <= maxPatchLevelDiffMonths;
     }
 
-    /**
-     * Checks certificate revocation status.
-     *
-     * Queries status list from android.googleapis.com/attestation/status and checks for
-     * the existence of certificate's serial number. If serial number exists in map, then fail.
-     */
-    private final class AndroidRevocationStatusListChecker extends PKIXCertPathChecker {
-        private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
-        private static final String STATUS_PROPERTY_KEY = "status";
-        private static final String REASON_PROPERTY_KEY = "reason";
-        private String mStatusUrl;
-        private JSONObject mJsonStatusMap;
-
-        @Override
-        public void init(boolean forward) throws CertPathValidatorException {
-            mStatusUrl = getRevocationListUrl();
-            if (mStatusUrl == null || mStatusUrl.isEmpty()) {
-                throw new CertPathValidatorException(
-                        "R.string.vendor_required_attestation_revocation_list_url is empty.");
-            }
-            // TODO(b/221067843): Update to only pull status map on non critical path and if
-            // out of date (24hrs).
-            mJsonStatusMap = getStatusMap(mStatusUrl);
-        }
-
-        @Override
-        public boolean isForwardCheckingSupported() {
-            return false;
-        }
-
-        @Override
-        public Set<String> getSupportedExtensions() {
-            return null;
-        }
-
-        @Override
-        public void check(Certificate cert, Collection<String> unresolvedCritExts)
-                throws CertPathValidatorException {
-            X509Certificate x509Certificate = (X509Certificate) cert;
-            // The json key is the certificate's serial number converted to lowercase hex.
-            String serialNumber = x509Certificate.getSerialNumber().toString(16);
-
-            if (serialNumber == null) {
-                throw new CertPathValidatorException("Certificate serial number can not be null.");
-            }
-
-            if (mJsonStatusMap.has(serialNumber)) {
-                JSONObject revocationStatus;
-                String status;
-                String reason;
-                try {
-                    revocationStatus = mJsonStatusMap.getJSONObject(serialNumber);
-                    status = revocationStatus.getString(STATUS_PROPERTY_KEY);
-                    reason = revocationStatus.getString(REASON_PROPERTY_KEY);
-                } catch (Throwable t) {
-                    throw new CertPathValidatorException("Unable get properties for certificate "
-                            + "with serial number " + serialNumber);
-                }
-                throw new CertPathValidatorException(
-                        "Invalid certificate with serial number " + serialNumber
-                                + " has status " + status
-                                + " because reason " + reason);
-            }
-        }
-
-        private JSONObject getStatusMap(String stringUrl) throws CertPathValidatorException {
-            URL url;
-            try {
-                url = new URL(stringUrl);
-            } catch (Throwable t) {
-                throw new CertPathValidatorException(
-                        "Unable to get revocation status from " + mStatusUrl, t);
-            }
-
-            try (InputStream inputStream = url.openStream()) {
-                JSONObject statusListJson = new JSONObject(
-                        new String(inputStream.readAllBytes(), UTF_8));
-                return statusListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
-            } catch (Throwable t) {
-                throw new CertPathValidatorException(
-                        "Unable to parse revocation status from " + mStatusUrl, t);
-            }
-        }
-
-        private String getRevocationListUrl() {
-            return mContext.getResources().getString(
-                    R.string.vendor_required_attestation_revocation_list_url);
-        }
-    }
-
     /* Mutable data class for tracking dump data from verifications. */
     private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
 
diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
new file mode 100644
index 0000000..d36d9f5
--- /dev/null
+++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2025 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.security;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Environment;
+import android.os.PersistableBundle;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.X509Certificate;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Manages the revocation status of certificates used in remote attestation. */
+class CertificateRevocationStatusManager {
+    private static final String TAG = "AVF_CRL";
+    // Must be unique within system server
+    private static final int JOB_ID = 1737671340;
+    private static final String REVOCATION_STATUS_FILE_NAME = "certificate_revocation_status.txt";
+    private static final String REVOCATION_STATUS_FILE_FIELD_DELIMITER = ",";
+
+    /**
+     * The number of days since last update for which a stored revocation status can be accepted.
+     */
+    @VisibleForTesting static final int MAX_DAYS_SINCE_LAST_CHECK = 30;
+
+    /**
+     * The number of days since issue date for an intermediary certificate to be considered fresh
+     * and not require a revocation list check.
+     */
+    private static final int FRESH_INTERMEDIARY_CERT_DAYS = 70;
+
+    /**
+     * The expected number of days between a certificate's issue date and notBefore date. Used to
+     * infer a certificate's issue date from its notBefore date.
+     */
+    private static final int DAYS_BETWEEN_ISSUE_AND_NOT_BEFORE_DATES = 2;
+
+    private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
+    private static final Object sFileLock = new Object();
+
+    private final Context mContext;
+    private final String mTestRemoteRevocationListUrl;
+    private final File mTestRevocationStatusFile;
+    private final boolean mShouldScheduleJob;
+
+    CertificateRevocationStatusManager(Context context) {
+        this(context, null, null, true);
+    }
+
+    @VisibleForTesting
+    CertificateRevocationStatusManager(
+            Context context,
+            String testRemoteRevocationListUrl,
+            File testRevocationStatusFile,
+            boolean shouldScheduleJob) {
+        mContext = context;
+        mTestRemoteRevocationListUrl = testRemoteRevocationListUrl;
+        mTestRevocationStatusFile = testRevocationStatusFile;
+        mShouldScheduleJob = shouldScheduleJob;
+    }
+
+    /**
+     * Check the revocation status of the provided {@link X509Certificate}s.
+     *
+     * <p>The provided certificates should have been validated and ordered from leaf to a
+     * certificate issued by the trust anchor, per the convention specified in the javadoc of {@link
+     * java.security.cert.CertPath}.
+     *
+     * @param certificates List of certificates to be checked
+     * @throws CertPathValidatorException if the check failed
+     */
+    void checkRevocationStatus(List<X509Certificate> certificates)
+            throws CertPathValidatorException {
+        if (!needToCheckRevocationStatus(certificates)) {
+            return;
+        }
+        List<String> serialNumbers = new ArrayList<>();
+        for (X509Certificate certificate : certificates) {
+            String serialNumber = certificate.getSerialNumber().toString(16);
+            if (serialNumber == null) {
+                throw new CertPathValidatorException("Certificate serial number cannot be null.");
+            }
+            serialNumbers.add(serialNumber);
+        }
+        try {
+            JSONObject revocationList = fetchRemoteRevocationList();
+            Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+            for (String serialNumber : serialNumbers) {
+                areCertificatesRevoked.put(serialNumber, revocationList.has(serialNumber));
+            }
+            updateLastRevocationCheckData(areCertificatesRevoked);
+            for (Map.Entry<String, Boolean> entry : areCertificatesRevoked.entrySet()) {
+                if (entry.getValue()) {
+                    throw new CertPathValidatorException(
+                            "Certificate " + entry.getKey() + " has been revoked.");
+                }
+            }
+        } catch (IOException | JSONException ex) {
+            Slog.d(TAG, "Fallback to check stored revocation status", ex);
+            if (ex instanceof IOException && mShouldScheduleJob) {
+                scheduleJobToUpdateStoredDataWithRemoteRevocationList(serialNumbers);
+            }
+            for (X509Certificate certificate : certificates) {
+                // Assume recently issued certificates are not revoked.
+                if (isIssuedWithinDays(certificate, MAX_DAYS_SINCE_LAST_CHECK)) {
+                    String serialNumber = certificate.getSerialNumber().toString(16);
+                    serialNumbers.remove(serialNumber);
+                }
+            }
+            Map<String, LocalDateTime> lastRevocationCheckData;
+            try {
+                lastRevocationCheckData = getLastRevocationCheckData();
+            } catch (IOException ex2) {
+                throw new CertPathValidatorException(
+                        "Unable to load stored revocation status", ex2);
+            }
+            for (String serialNumber : serialNumbers) {
+                if (!lastRevocationCheckData.containsKey(serialNumber)
+                        || lastRevocationCheckData
+                                .get(serialNumber)
+                                .isBefore(
+                                        LocalDateTime.now().minusDays(MAX_DAYS_SINCE_LAST_CHECK))) {
+                    throw new CertPathValidatorException(
+                            "Unable to verify the revocation status of certificate "
+                                    + serialNumber);
+                }
+            }
+        }
+    }
+
+    private static boolean needToCheckRevocationStatus(
+            List<X509Certificate> certificatesOrderedLeafFirst) {
+        if (certificatesOrderedLeafFirst.isEmpty()) {
+            return false;
+        }
+        // A certificate isn't revoked when it is first issued, so we treat it as checked on its
+        // issue date.
+        if (!isIssuedWithinDays(certificatesOrderedLeafFirst.get(0), MAX_DAYS_SINCE_LAST_CHECK)) {
+            return true;
+        }
+        for (int i = 1; i < certificatesOrderedLeafFirst.size(); i++) {
+            if (!isIssuedWithinDays(
+                    certificatesOrderedLeafFirst.get(i), FRESH_INTERMEDIARY_CERT_DAYS)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isIssuedWithinDays(X509Certificate certificate, int days) {
+        LocalDate notBeforeDate =
+                LocalDate.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault());
+        LocalDate expectedIssueData =
+                notBeforeDate.plusDays(DAYS_BETWEEN_ISSUE_AND_NOT_BEFORE_DATES);
+        return LocalDate.now().minusDays(days + 1).isBefore(expectedIssueData);
+    }
+
+    void updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+            JSONObject revocationList, Collection<String> otherCertificatesToCheck) {
+        Set<String> allCertificatesToCheck = new HashSet<>(otherCertificatesToCheck);
+        try {
+            allCertificatesToCheck.addAll(getLastRevocationCheckData().keySet());
+        } catch (IOException ex) {
+            Slog.e(TAG, "Unable to update last check date of stored data.", ex);
+        }
+        Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+        for (String serialNumber : allCertificatesToCheck) {
+            areCertificatesRevoked.put(serialNumber, revocationList.has(serialNumber));
+        }
+        updateLastRevocationCheckData(areCertificatesRevoked);
+    }
+
+    /**
+     * Update the last revocation check data stored on this device.
+     *
+     * @param areCertificatesRevoked A Map whose keys are certificate serial numbers and values are
+     *     whether that certificate has been revoked
+     */
+    void updateLastRevocationCheckData(Map<String, Boolean> areCertificatesRevoked) {
+        LocalDateTime now = LocalDateTime.now();
+        synchronized (sFileLock) {
+            Map<String, LocalDateTime> lastRevocationCheckData;
+            try {
+                lastRevocationCheckData = getLastRevocationCheckData();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Unable to updateLastRevocationCheckData", ex);
+                return;
+            }
+            for (Map.Entry<String, Boolean> entry : areCertificatesRevoked.entrySet()) {
+                if (entry.getValue()) {
+                    lastRevocationCheckData.remove(entry.getKey());
+                } else {
+                    lastRevocationCheckData.put(entry.getKey(), now);
+                }
+            }
+            storeLastRevocationCheckData(lastRevocationCheckData);
+        }
+    }
+
+    Map<String, LocalDateTime> getLastRevocationCheckData() throws IOException {
+        Map<String, LocalDateTime> data = new HashMap<>();
+        File dataFile = getLastRevocationCheckDataFile();
+        synchronized (sFileLock) {
+            if (!dataFile.exists()) {
+                return data;
+            }
+            String dataString;
+            try (FileInputStream in = new FileInputStream(dataFile)) {
+                dataString = new String(in.readAllBytes(), UTF_8);
+            }
+            for (String line : dataString.split(System.lineSeparator())) {
+                String[] elements = line.split(REVOCATION_STATUS_FILE_FIELD_DELIMITER);
+                if (elements.length != 2) {
+                    continue;
+                }
+                try {
+                    data.put(elements[0], LocalDateTime.parse(elements[1]));
+                } catch (DateTimeParseException ex) {
+                    Slog.e(
+                            TAG,
+                            "Unable to parse last checked LocalDateTime from file. Deleting the"
+                                    + " potentially corrupted file.",
+                            ex);
+                    dataFile.delete();
+                    return data;
+                }
+            }
+        }
+        return data;
+    }
+
+    @VisibleForTesting
+    void storeLastRevocationCheckData(Map<String, LocalDateTime> lastRevocationCheckData) {
+        StringBuilder dataStringBuilder = new StringBuilder();
+        for (Map.Entry<String, LocalDateTime> entry : lastRevocationCheckData.entrySet()) {
+            dataStringBuilder
+                    .append(entry.getKey())
+                    .append(REVOCATION_STATUS_FILE_FIELD_DELIMITER)
+                    .append(entry.getValue())
+                    .append(System.lineSeparator());
+        }
+        synchronized (sFileLock) {
+            try (FileOutputStream fileOutputStream =
+                    new FileOutputStream(getLastRevocationCheckDataFile())) {
+                fileOutputStream.write(dataStringBuilder.toString().getBytes(UTF_8));
+                Slog.d(TAG, "Successfully stored revocation status data.");
+            } catch (IOException ex) {
+                Slog.e(TAG, "Failed to store revocation status data.", ex);
+            }
+        }
+    }
+
+    private File getLastRevocationCheckDataFile() {
+        if (mTestRevocationStatusFile != null) {
+            return mTestRevocationStatusFile;
+        }
+        return new File(Environment.getDataSystemDirectory(), REVOCATION_STATUS_FILE_NAME);
+    }
+
+    private void scheduleJobToUpdateStoredDataWithRemoteRevocationList(List<String> serialNumbers) {
+        JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+        if (jobScheduler == null) {
+            Slog.e(TAG, "Unable to get job scheduler.");
+            return;
+        }
+        Slog.d(TAG, "Scheduling job to fetch remote CRL.");
+        PersistableBundle extras = new PersistableBundle();
+        extras.putStringArray(
+                UpdateCertificateRevocationStatusJobService.EXTRA_KEY_CERTIFICATES_TO_CHECK,
+                serialNumbers.toArray(new String[0]));
+        jobScheduler.schedule(
+                new JobInfo.Builder(
+                                JOB_ID,
+                                new ComponentName(
+                                        mContext,
+                                        UpdateCertificateRevocationStatusJobService.class))
+                        .setExtras(extras)
+                        .setRequiredNetwork(
+                                new NetworkRequest.Builder()
+                                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                                        .build())
+                        .build());
+    }
+
+    /**
+     * Fetches the revocation list from the URL specified in
+     * R.string.vendor_required_attestation_revocation_list_url
+     *
+     * @return The remote revocation list entries in a JSONObject
+     * @throws CertPathValidatorException if the URL is not defined or is malformed.
+     * @throws IOException if the URL is valid but the fetch failed.
+     * @throws JSONException if the revocation list content cannot be parsed
+     */
+    JSONObject fetchRemoteRevocationList()
+            throws CertPathValidatorException, IOException, JSONException {
+        String urlString = getRemoteRevocationListUrl();
+        if (urlString == null || urlString.isEmpty()) {
+            throw new CertPathValidatorException(
+                    "R.string.vendor_required_attestation_revocation_list_url is empty.");
+        }
+        URL url;
+        try {
+            url = new URL(urlString);
+        } catch (MalformedURLException ex) {
+            throw new CertPathValidatorException("Unable to parse the URL " + urlString, ex);
+        }
+        byte[] revocationListBytes;
+        try (InputStream inputStream = url.openStream()) {
+            revocationListBytes = inputStream.readAllBytes();
+        }
+        JSONObject revocationListJson = new JSONObject(new String(revocationListBytes, UTF_8));
+        return revocationListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
+    }
+
+    private String getRemoteRevocationListUrl() {
+        if (mTestRemoteRevocationListUrl != null) {
+            return mTestRemoteRevocationListUrl;
+        }
+        return mContext.getResources()
+                .getString(R.string.vendor_required_attestation_revocation_list_url);
+    }
+}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index fa4bf22..7a31a00 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -3,5 +3,6 @@
 include /core/java/android/security/OWNERS
 
 per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
+per-file *CertificateRevocationStatus* = file:/core/java/android/security/attestationverification/OWNERS
 per-file FileIntegrity*.java = victorhsieh@google.com
 per-file KeyChainSystemService.java = file:platform/packages/apps/KeyChain:/OWNERS
diff --git a/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java b/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java
new file mode 100644
index 0000000..768c812
--- /dev/null
+++ b/services/core/java/com/android/server/security/UpdateCertificateRevocationStatusJobService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 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.security;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** A {@link JobService} that fetches the certificate revocation list from a remote location. */
+public class UpdateCertificateRevocationStatusJobService extends JobService {
+
+    static final String EXTRA_KEY_CERTIFICATES_TO_CHECK =
+            "com.android.server.security.extra.CERTIFICATES_TO_CHECK";
+    private static final String TAG = "AVF_CRL";
+    private ExecutorService mExecutorService;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mExecutorService = Executors.newSingleThreadExecutor();
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        mExecutorService.execute(
+                () -> {
+                    try {
+                        CertificateRevocationStatusManager certificateRevocationStatusManager =
+                                new CertificateRevocationStatusManager(this);
+                        Slog.d(TAG, "Starting to fetch remote CRL from job service.");
+                        JSONObject revocationList =
+                                certificateRevocationStatusManager.fetchRemoteRevocationList();
+                        String[] certificatesToCheckFromJobParams =
+                                params.getExtras().getStringArray(EXTRA_KEY_CERTIFICATES_TO_CHECK);
+                        if (certificatesToCheckFromJobParams == null) {
+                            Slog.e(TAG, "Extras not found: " + EXTRA_KEY_CERTIFICATES_TO_CHECK);
+                            return;
+                        }
+                        certificateRevocationStatusManager
+                                .updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+                                        revocationList,
+                                        Arrays.asList(certificatesToCheckFromJobParams));
+                    } catch (Throwable t) {
+                        Slog.e(TAG, "Unable to update the stored revocation status.", t);
+                    }
+                    jobFinished(params, false);
+                });
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mExecutorService.shutdown();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 20917ba..cf9c57a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -113,7 +113,6 @@
 import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
 import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
 import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.hasWindowExtensionsEnabled;
@@ -247,7 +246,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -659,8 +657,6 @@
 
     private RemoteAnimationDefinition mRemoteAnimationDefinition;
 
-    AnimatingActivityRegistry mAnimatingActivityRegistry;
-
     // Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task
     // due to picture-in-picture. This gets cleared whenever this activity or the Task
     // it references to gets removed. This should also be cleared when we move out of pip.
@@ -857,12 +853,6 @@
     })
     @interface SplashScreenBehavior { }
 
-    // Force an app transition to be ran in the case the visibility of the app did not change.
-    // We use this for the case of moving a Root Task to the back with multiple activities, and the
-    // top activity enters PIP; the bottom activity's visibility stays the same, but we need to
-    // run the transition.
-    boolean mRequestForceTransition;
-
     boolean mEnteringAnimation;
     boolean mOverrideTaskTransition;
     boolean mDismissKeyguardIfInsecure;
@@ -1587,9 +1577,6 @@
             }
         }
         final Task rootTask = getRootTask();
-
-        updateAnimatingActivityRegistry();
-
         if (task == mLastParentBeforePip && task != null) {
             // Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
             mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
@@ -1691,20 +1678,6 @@
         return !organizedTaskFragment.isAllowedToEmbedActivityInTrustedMode(this);
     }
 
-    void updateAnimatingActivityRegistry() {
-        final Task rootTask = getRootTask();
-        final AnimatingActivityRegistry registry = rootTask != null
-                ? rootTask.getAnimatingActivityRegistry()
-                : null;
-
-        // If we reparent, make sure to remove ourselves from the old animation registry.
-        if (mAnimatingActivityRegistry != null && mAnimatingActivityRegistry != registry) {
-            mAnimatingActivityRegistry.notifyFinished(this);
-        }
-
-        mAnimatingActivityRegistry = registry;
-    }
-
     boolean canAutoEnterPip() {
         // beforeStopping=false since the actual pip-ing will take place after startPausing()
         final boolean activityCanPip = checkEnterPictureInPictureState(
@@ -1789,7 +1762,6 @@
         if (prevDc.mOpeningApps.remove(this)) {
             // Transfer opening transition to new display.
             mDisplayContent.mOpeningApps.add(this);
-            mDisplayContent.transferAppTransitionFrom(prevDc);
             mDisplayContent.executeAppTransition();
         }
 
@@ -4632,12 +4604,6 @@
                 }
             }
 
-            // In this case, the starting icon has already been displayed, so start
-            // letting windows get shown immediately without any more transitions.
-            if (fromActivity.mVisible) {
-                mDisplayContent.mSkipAppTransitionAnimation = true;
-            }
-
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s"
                     + " from %s to %s", tStartingWindow, fromActivity, this);
 
@@ -5668,76 +5634,17 @@
             mTransitionController.mValidateCommitVis.add(this);
             return;
         }
-        // If we are preparing an app transition, then delay changing
-        // the visibility of this token until we execute that transition.
-        if (deferCommitVisibilityChange(visible)) {
-            return;
-        }
 
         commitVisibility(visible, true /* performLayout */);
         updateReportedVisibilityLocked();
     }
 
-    /**
-     * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
-     * Then its visibility will be committed until the transition is ready.
-     */
-    private boolean deferCommitVisibilityChange(boolean visible) {
-        if (mTransitionController.isShellTransitionsEnabled()) {
-            // Shell transition doesn't use opening/closing sets.
-            return false;
-        }
-        if (!mDisplayContent.mAppTransition.isTransitionSet()) {
-            return false;
-        }
-        if (mWaitForEnteringPinnedMode && mVisible == visible) {
-            // If the visibility is not changed during enter PIP, we don't want to include it in
-            // app transition to affect the animation theme, because the Pip organizer will
-            // animate the entering PIP instead.
-            return false;
-        }
-
-        // The animation will be visible soon so do not skip by screen off.
-        final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
-                .isKeyguardGoingAway(mDisplayContent.mDisplayId);
-        // Ignore display frozen so the opening / closing transition type can be updated correctly
-        // even if the display is frozen. And it's safe since in applyAnimation will still check
-        // DC#okToAnimate again if the transition animation is fine to apply.
-        if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
-            return false;
-        }
-        if (visible) {
-            mDisplayContent.mOpeningApps.add(this);
-            mEnteringAnimation = true;
-        } else if (mVisible) {
-            mDisplayContent.mClosingApps.add(this);
-            mEnteringAnimation = false;
-        }
-        if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
-            // Add the launching-behind activity to mOpeningApps.
-            final WindowState win = mDisplayContent.findFocusedWindow();
-            if (win != null) {
-                final ActivityRecord focusedActivity = win.mActivityRecord;
-                if (focusedActivity != null) {
-                    ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
-                            "TRANSIT_FLAG_OPEN_BEHIND,  adding %s to mOpeningApps",
-                            focusedActivity);
-                    // Force animation to be loaded.
-                    mDisplayContent.mOpeningApps.add(focusedActivity);
-                }
-            }
-        }
-        return true;
-    }
-
     @Override
     boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
             boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
         if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
             return false;
         }
-        // If it was set to true, reset the last request to force the transition.
-        mRequestForceTransition = false;
         return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
     }
 
@@ -5900,27 +5807,6 @@
     }
 
     /**
-     * Check if visibility of this {@link ActivityRecord} should be updated as part of an app
-     * transition.
-     *
-     * <p class="note><strong>Note:</strong> If the visibility of this {@link ActivityRecord} is
-     * already set to {@link #mVisible}, we don't need to update the visibility. So {@code false} is
-     * returned.</p>
-     *
-     * @param visible {@code true} if this {@link ActivityRecord} should become visible,
-     *                {@code false} if this should become invisible.
-     * @return {@code true} if visibility of this {@link ActivityRecord} should be updated, and
-     *         an app transition animation should be run.
-     */
-    boolean shouldApplyAnimation(boolean visible) {
-        // Allow for state update and animation to be applied if:
-        // * activity is transitioning visibility state
-        // * or the activity was marked as hidden and is exiting before we had a chance to play the
-        // transition animation
-        return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting);
-    }
-
-    /**
      * See {@link Activity#setRecentsScreenshotEnabled}.
      */
     void setRecentsScreenshotEnabled(boolean enabled) {
@@ -6208,13 +6094,8 @@
             return false;
         }
 
-        // Hide all activities on the presenting display so that malicious apps can't do tap
-        // jacking (b/391466268).
-        // For now, this should only be applied to external displays because presentations can only
-        // be shown on them.
-        // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
-        // the presentation won't stop its controlling activity.
-        if (enablePresentationForConnectedDisplays() && mDisplayContent.mIsPresenting) {
+        // A presentation stopps all activities behind on the same display.
+        if (mWmService.mPresentationController.shouldOccludeActivities(getDisplayId())) {
             return false;
         }
 
@@ -7635,13 +7516,6 @@
     }
 
     @Override
-    public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
-        return mAnimatingActivityRegistry != null
-                && mAnimatingActivityRegistry.notifyAboutToFinish(
-                this, endDeferFinishCallback);
-    }
-
-    @Override
     boolean isWaitingForTransitionStart() {
         final DisplayContent dc = getDisplayContent();
         return dc != null && dc.mAppTransition.isTransitionSet()
@@ -7662,10 +7536,6 @@
 
     @Override
     public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
-        if (mAnimatingActivityRegistry != null) {
-            mAnimatingActivityRegistry.notifyStarting(this);
-        }
-
         if (mNeedsLetterboxedAnimation) {
             updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
             mNeedsAnimationBoundsLayer = true;
@@ -7676,17 +7546,7 @@
         // new layer.
         if (mNeedsAnimationBoundsLayer) {
             mTmpRect.setEmpty();
-            if (getDisplayContent().mAppTransitionController.isTransitWithinTask(
-                    getTransit(), task)) {
-                task.getBounds(mTmpRect);
-            } else {
-                final Task rootTask = getRootTask();
-                if (rootTask == null) {
-                    return;
-                }
-                // Set clip rect to root task bounds.
-                rootTask.getBounds(mTmpRect);
-            }
+            task.getBounds(mTmpRect);
             mAnimationBoundsLayer = createAnimationBoundsLayer(t);
 
             // Crop to root task bounds.
@@ -7842,10 +7702,6 @@
             mNeedsLetterboxedAnimation = false;
             updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
         }
-
-        if (mAnimatingActivityRegistry != null) {
-            mAnimatingActivityRegistry.notifyFinished(this);
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 254127d..819e117 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4128,22 +4128,7 @@
     @Override
     public void registerRemoteAnimationsForDisplay(int displayId,
             RemoteAnimationDefinition definition) {
-        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
-                "registerRemoteAnimations");
-        definition.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
-        synchronized (mGlobalLock) {
-            final DisplayContent display = mRootWindowContainer.getDisplayContent(displayId);
-            if (display == null) {
-                Slog.e(TAG, "Couldn't find display with id: " + displayId);
-                return;
-            }
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                display.registerRemoteAnimations(definition);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
+        // TODO(b/365884835): Remove callers.
     }
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
diff --git a/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java b/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java
deleted file mode 100644
index 18ec96c..0000000
--- a/services/core/java/com/android/server/wm/AnimatingActivityRegistry.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Keeps track of all {@link ActivityRecord} that are animating and makes sure all animations are
- * finished at the same time such that we don't run into issues with z-ordering: An activity A
- * that has a shorter animation that is above another activity B with a longer animation in the same
- * task, the animation layer would put the B on top of A, but from the hierarchy, A needs to be on
- * top of B. Thus, we defer reparenting A to the original hierarchy such that it stays on top of B
- * until B finishes animating.
- */
-class AnimatingActivityRegistry {
-
-    private ArraySet<ActivityRecord> mAnimatingActivities = new ArraySet<>();
-    private ArrayMap<ActivityRecord, Runnable> mFinishedTokens = new ArrayMap<>();
-
-    private ArrayList<Runnable> mTmpRunnableList = new ArrayList<>();
-
-    private boolean mEndingDeferredFinish;
-
-    /**
-     * Notifies that an {@link ActivityRecord} has started animating.
-     */
-    void notifyStarting(ActivityRecord token) {
-        mAnimatingActivities.add(token);
-    }
-
-    /**
-     * Notifies that an {@link ActivityRecord} has finished animating.
-     */
-    void notifyFinished(ActivityRecord activity) {
-        mAnimatingActivities.remove(activity);
-        mFinishedTokens.remove(activity);
-
-        // If we were the last activity, make sure the end all deferred finishes.
-        if (mAnimatingActivities.isEmpty()) {
-            endDeferringFinished();
-        }
-    }
-
-    /**
-     * Called when an {@link ActivityRecord} is about to finish animating.
-     *
-     * @param endDeferFinishCallback Callback to run when defer finish should be ended.
-     * @return {@code true} if finishing the animation should be deferred, {@code false} otherwise.
-     */
-    boolean notifyAboutToFinish(ActivityRecord activity, Runnable endDeferFinishCallback) {
-        final boolean removed = mAnimatingActivities.remove(activity);
-        if (!removed) {
-            return false;
-        }
-
-        if (mAnimatingActivities.isEmpty()) {
-
-            // If no animations are animating anymore, finish all others.
-            endDeferringFinished();
-            return false;
-        } else {
-
-            // Otherwise let's put it into the pending list of to be finished animations.
-            mFinishedTokens.put(activity, endDeferFinishCallback);
-            return true;
-        }
-    }
-
-    private void endDeferringFinished() {
-
-        // Don't start recursing. Running the finished listener invokes notifyFinished, which may
-        // invoked us again.
-        if (mEndingDeferredFinish) {
-            return;
-        }
-        try {
-            mEndingDeferredFinish = true;
-
-            // Copy it into a separate temp list to avoid modifying the collection while iterating
-            // as calling the callback may call back into notifyFinished.
-            for (int i = mFinishedTokens.size() - 1; i >= 0; i--) {
-                mTmpRunnableList.add(mFinishedTokens.valueAt(i));
-            }
-            mFinishedTokens.clear();
-            for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) {
-                mTmpRunnableList.get(i).run();
-            }
-            mTmpRunnableList.clear();
-        } finally {
-            mEndingDeferredFinish = false;
-        }
-    }
-
-    void dump(PrintWriter pw, String header, String prefix) {
-        if (!mAnimatingActivities.isEmpty() || !mFinishedTokens.isEmpty()) {
-            pw.print(prefix); pw.println(header);
-            prefix = prefix + "  ";
-            pw.print(prefix); pw.print("mAnimatingActivities="); pw.println(mAnimatingActivities);
-            pw.print(prefix); pw.print("mFinishedTokens="); pw.println(mFinishedTokens);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 3dc377d..4458ed7 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -109,15 +109,6 @@
      * Gets called when the animation is about to finish and gives the client the opportunity to
      * defer finishing the animation, i.e. it keeps the leash around until the client calls
      * endDeferFinishCallback.
-     * <p>
-     * This has the same effect as
-     * {@link com.android.server.wm.SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}
-     * . The later will be evaluated first and has precedence over this method if it returns true,
-     * which means that if the {@link com.android.server.wm.SurfaceAnimator.Animatable} requests to
-     * defer its finish, this method won't be called so this adapter will never have access to the
-     * finish callback. On the other hand, if the
-     * {@link com.android.server.wm.SurfaceAnimator.Animatable}, doesn't request to defer, this
-     * {@link AnimationAdapter} is responsible for ending the animation.
      *
      * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
      * @return Whether the client would like to defer the animation finish.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 932f268..9c4b722 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1462,7 +1462,7 @@
     }
 
     boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
-        if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+        if (WindowManagerService.sEnableShellTransitions) {
             return false;
         }
         mNextAppTransitionRequests.add(transit);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
deleted file mode 100644
index d5fe056..0000000
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ /dev/null
@@ -1,1352 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static com.android.server.wm.AppTransition.isNormalTransit;
-import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
-import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.Trace;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.WindowManager.TransitionFlags;
-import android.view.WindowManager.TransitionOldType;
-import android.view.WindowManager.TransitionType;
-import android.window.ITaskFragmentOrganizer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Checks for app transition readiness, resolves animation attributes and performs visibility
- * change for apps that animate as part of an app transition.
- */
-public class AppTransitionController {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransitionController" : TAG_WM;
-    private final WindowManagerService mService;
-    private final DisplayContent mDisplayContent;
-    private final WallpaperController mWallpaperControllerLocked;
-    private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
-
-    private static final int TYPE_NONE = 0;
-    private static final int TYPE_ACTIVITY = 1;
-    private static final int TYPE_TASK_FRAGMENT = 2;
-    private static final int TYPE_TASK = 3;
-
-    @IntDef(prefix = { "TYPE_" }, value = {
-            TYPE_NONE,
-            TYPE_ACTIVITY,
-            TYPE_TASK_FRAGMENT,
-            TYPE_TASK
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface TransitContainerType {}
-
-    private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
-    private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
-
-    AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
-        mService = service;
-        mDisplayContent = displayContent;
-        mWallpaperControllerLocked = mDisplayContent.mWallpaperController;
-    }
-
-    void registerRemoteAnimations(RemoteAnimationDefinition definition) {
-        mRemoteAnimationDefinition = definition;
-    }
-
-    /**
-     * Returns the currently visible window that is associated with the wallpaper in case we are
-     * transitioning from an activity with a wallpaper to one without.
-     */
-    @Nullable
-    private WindowState getOldWallpaper() {
-        final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
-        final @TransitionType int firstTransit =
-                mDisplayContent.mAppTransition.getFirstAppTransition();
-
-        final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
-                mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, true /* visible */);
-        final boolean showWallpaper = wallpaperTarget != null
-                && (wallpaperTarget.hasWallpaper()
-                // Update task open transition to wallpaper transition when wallpaper is visible.
-                // (i.e.launching app info activity from recent tasks)
-                || ((firstTransit == TRANSIT_OPEN || firstTransit == TRANSIT_TO_FRONT)
-                && (!openingWcs.isEmpty() && openingWcs.valueAt(0).asTask() != null)
-                && mWallpaperControllerLocked.isWallpaperVisible()));
-        // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
-        // don't consider upgrading to wallpaper transition.
-        return (mWallpaperControllerLocked.isWallpaperTargetAnimating() || !showWallpaper)
-                ? null : wallpaperTarget;
-    }
-
-    /**
-     * Handle application transition for given display.
-     */
-    void handleAppTransitionReady() {
-        mTempTransitionReasons.clear();
-        if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
-                || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
-                || !transitionGoodToGoForTaskFragments()) {
-            return;
-        }
-
-        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
-
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
-        // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause.
-        mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow,
-                true /* traverseTopToBottom */);
-        // TODO(new-app-transition): Remove code using appTransition.getAppTransition()
-        final AppTransition appTransition = mDisplayContent.mAppTransition;
-
-        mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear();
-
-        appTransition.removeAppTransitionTimeoutCallbacks();
-
-        mDisplayContent.mWallpaperMayChange = false;
-
-        int appCount = mDisplayContent.mOpeningApps.size();
-        for (int i = 0; i < appCount; ++i) {
-            // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
-            // window is removed, or window relayout to invisible. This also affects window
-            // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
-            // transition selection depends on wallpaper target visibility.
-            mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
-        }
-        appCount = mDisplayContent.mChangingContainers.size();
-        for (int i = 0; i < appCount; ++i) {
-            // Clearing for same reason as above.
-            final ActivityRecord activity = getAppFromContainer(
-                    mDisplayContent.mChangingContainers.valueAtUnchecked(i));
-            if (activity != null) {
-                activity.clearAnimatingFlags();
-            }
-        }
-
-        // Adjust wallpaper before we pull the lower/upper target, since pending changes
-        // (like the clearAnimatingFlags() above) might affect wallpaper target result.
-        // Or, the opening app window should be a wallpaper target.
-        mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
-                mDisplayContent.mOpeningApps);
-
-        ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
-        ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
-        if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringFinishTransition()) {
-            tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
-            tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
-        }
-
-        @TransitionOldType final int transit = getTransitCompatType(
-                mDisplayContent.mAppTransition, tmpOpenApps,
-                tmpCloseApps, mDisplayContent.mChangingContainers,
-                mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
-                mDisplayContent.mSkipAppTransitionAnimation);
-        mDisplayContent.mSkipAppTransitionAnimation = false;
-
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "handleAppTransitionReady: displayId=%d appTransition={%s}"
-                + " openingApps=[%s] closingApps=[%s] transit=%s",
-                mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps,
-                tmpCloseApps, AppTransition.appTransitionOldToString(transit));
-
-        // Find the layout params of the top-most application window in the tokens, which is
-        // what will control the animation theme. If all closing windows are obscured, then there is
-        // no need to do an animation. This is the case, for example, when this transition is being
-        // done behind a dream window.
-        final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps,
-                tmpCloseApps, mDisplayContent.mChangingContainers);
-        final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
-                tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers);
-        final ActivityRecord topOpeningApp =
-                getTopApp(tmpOpenApps, false /* ignoreHidden */);
-        final ActivityRecord topClosingApp =
-                getTopApp(tmpCloseApps, false /* ignoreHidden */);
-        final ActivityRecord topChangingApp =
-                getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
-        final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
-
-        // Check if there is any override
-        if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
-            // Unfreeze the windows that were previously frozen for TaskFragment animation.
-            overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
-        }
-
-        final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
-                || containsVoiceInteraction(mDisplayContent.mOpeningApps);
-
-        final int layoutRedo;
-        mService.mSurfaceAnimationRunner.deferStartingAnimations();
-        try {
-            applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction);
-            handleClosingApps();
-            handleOpeningApps();
-            handleChangingApps(transit);
-            handleClosingChangingContainers();
-
-            appTransition.setLastAppTransition(transit, topOpeningApp,
-                    topClosingApp, topChangingApp);
-
-            final int flags = appTransition.getTransitFlags();
-            layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
-            appTransition.postAnimationCallback();
-        } finally {
-            appTransition.clear();
-            mService.mSurfaceAnimationRunner.continueStartingAnimations();
-        }
-
-        mService.mSnapshotController.onTransitionStarting(mDisplayContent);
-
-        mDisplayContent.mOpeningApps.clear();
-        mDisplayContent.mClosingApps.clear();
-        mDisplayContent.mChangingContainers.clear();
-        mDisplayContent.mUnknownAppVisibilityController.clear();
-        mDisplayContent.mClosingChangingContainers.clear();
-
-        // This has changed the visibility of windows, so perform
-        // a new layout to get them all up-to-date.
-        mDisplayContent.setLayoutNeeded();
-
-        mDisplayContent.computeImeTarget(true /* updateImeTarget */);
-
-        mService.mAtmService.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
-                mTempTransitionReasons);
-
-        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-
-        mDisplayContent.pendingLayoutChanges |=
-                layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
-    }
-
-    /**
-     * Get old transit type based on the current transit requests.
-     *
-     * @param appTransition {@link AppTransition} for managing app transition state.
-     * @param openingApps {@link ActivityRecord}s which are becoming visible.
-     * @param closingApps {@link ActivityRecord}s which are becoming invisible.
-     * @param changingContainers {@link WindowContainer}s which are changed in configuration.
-     * @param wallpaperTarget If non-null, this is the currently visible window that is associated
-     *                        with the wallpaper.
-     * @param oldWallpaper The currently visible window that is associated with the wallpaper in
-     *                     case we are transitioning from an activity with a wallpaper to one
-     *                     without. Otherwise null.
-     */
-    @TransitionOldType static int getTransitCompatType(AppTransition appTransition,
-            ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
-            ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
-            @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
-
-        final ActivityRecord topOpeningApp = getTopApp(openingApps, false /* ignoreHidden */);
-        final ActivityRecord topClosingApp = getTopApp(closingApps, true /* ignoreHidden */);
-
-        // Determine if closing and opening app token sets are wallpaper targets, in which case
-        // special animations are needed.
-        final boolean openingAppHasWallpaper = canBeWallpaperTarget(openingApps)
-                && wallpaperTarget != null;
-        final boolean closingAppHasWallpaper = canBeWallpaperTarget(closingApps)
-                && wallpaperTarget != null;
-
-        // Keyguard transit has high priority.
-        switch (appTransition.getKeyguardTransition()) {
-            case TRANSIT_KEYGUARD_GOING_AWAY:
-                return openingAppHasWallpaper ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
-                        : TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-            case TRANSIT_KEYGUARD_OCCLUDE:
-                // When there is a closing app, the keyguard has already been occluded by an
-                // activity, and another activity has started on top of that activity, so normal
-                // app transition animation should be used.
-                if (!closingApps.isEmpty()) {
-                    return TRANSIT_OLD_ACTIVITY_OPEN;
-                }
-                if (!openingApps.isEmpty() && openingApps.valueAt(0).getActivityType()
-                        == ACTIVITY_TYPE_DREAM) {
-                    return TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
-                }
-                return TRANSIT_OLD_KEYGUARD_OCCLUDE;
-            case TRANSIT_KEYGUARD_UNOCCLUDE:
-                return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-        }
-
-        // Determine whether the top opening and closing activity is a dream activity. If so, this
-        // has higher priority than others except keyguard transit.
-        if (topOpeningApp != null && topOpeningApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
-            return TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-        } else if (topClosingApp != null
-                && topClosingApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
-            return TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-        }
-
-        // This is not keyguard transition and one of the app has request to skip app transition.
-        if (skipAppTransitionAnimation) {
-            return WindowManager.TRANSIT_OLD_UNSET;
-        }
-        @TransitionFlags final int flags = appTransition.getTransitFlags();
-        @TransitionType final int firstTransit = appTransition.getFirstAppTransition();
-
-        // Special transitions
-        // TODO(new-app-transitions): Revisit if those can be rewritten by using flags.
-        if (appTransition.containsTransitRequest(TRANSIT_CHANGE) && !changingContainers.isEmpty()) {
-            @TransitContainerType int changingType =
-                    getTransitContainerType(changingContainers.valueAt(0));
-            switch (changingType) {
-                case TYPE_TASK:
-                    return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-                case TYPE_TASK_FRAGMENT:
-                    return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-                default:
-                    throw new IllegalStateException(
-                            "TRANSIT_CHANGE with unrecognized changing type=" + changingType);
-            }
-        }
-        if ((flags & TRANSIT_FLAG_APP_CRASHED) != 0) {
-            return TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-        }
-        if (firstTransit == TRANSIT_NONE) {
-            return TRANSIT_OLD_NONE;
-        }
-
-        /*
-         * There are cases where we open/close a new task/activity, but in reality only a
-         * translucent activity on top of existing activities is opening/closing. For that one, we
-         * have a different animation because non of the task/activity animations actually work well
-         * with translucent apps.
-         */
-        if (isNormalTransit(firstTransit)) {
-            boolean allOpeningVisible = true;
-            boolean allTranslucentOpeningApps = !openingApps.isEmpty();
-            for (int i = openingApps.size() - 1; i >= 0; i--) {
-                final ActivityRecord activity = openingApps.valueAt(i);
-                if (!activity.isVisible()) {
-                    allOpeningVisible = false;
-                    if (activity.fillsParent()) {
-                        allTranslucentOpeningApps = false;
-                    }
-                }
-            }
-            boolean allTranslucentClosingApps = !closingApps.isEmpty();
-            for (int i = closingApps.size() - 1; i >= 0; i--) {
-                if (closingApps.valueAt(i).fillsParent()) {
-                    allTranslucentClosingApps = false;
-                    break;
-                }
-            }
-
-            if (allTranslucentClosingApps && allOpeningVisible) {
-                return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
-            }
-            if (allTranslucentOpeningApps && closingApps.isEmpty()) {
-                return TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
-            }
-        }
-
-        if (closingAppHasWallpaper && openingAppHasWallpaper) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Wallpaper animation!");
-            switch (firstTransit) {
-                case TRANSIT_OPEN:
-                case TRANSIT_TO_FRONT:
-                    return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
-                case TRANSIT_CLOSE:
-                case TRANSIT_TO_BACK:
-                    return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
-            }
-        } else if (oldWallpaper != null && !openingApps.isEmpty()
-                && !openingApps.contains(oldWallpaper.mActivityRecord)
-                && closingApps.contains(oldWallpaper.mActivityRecord)
-                && topClosingApp == oldWallpaper.mActivityRecord) {
-            // We are transitioning from an activity with a wallpaper to one without.
-            return TRANSIT_OLD_WALLPAPER_CLOSE;
-        } else if (wallpaperTarget != null && wallpaperTarget.isVisible()
-                && openingApps.contains(wallpaperTarget.mActivityRecord)
-                && topOpeningApp == wallpaperTarget.mActivityRecord
-                /* && transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE */) {
-            // We are transitioning from an activity without
-            // a wallpaper to now showing the wallpaper
-            return TRANSIT_OLD_WALLPAPER_OPEN;
-        }
-
-        final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
-                openingApps, closingApps, true /* visible */);
-        final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
-                openingApps, closingApps, false /* visible */);
-        final WindowContainer<?> openingContainer = !openingWcs.isEmpty()
-                ? openingWcs.valueAt(0) : null;
-        final WindowContainer<?> closingContainer = !closingWcs.isEmpty()
-                ? closingWcs.valueAt(0) : null;
-        @TransitContainerType int openingType = getTransitContainerType(openingContainer);
-        @TransitContainerType int closingType = getTransitContainerType(closingContainer);
-        if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) {
-            if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) {
-                // If we are opening the home task, we want to play an animation as if
-                // the task on top is being brought to back.
-                return TRANSIT_OLD_TASK_TO_BACK;
-            }
-            return TRANSIT_OLD_TASK_TO_FRONT;
-        }
-        if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) {
-            return TRANSIT_OLD_TASK_TO_BACK;
-        }
-        if (appTransition.containsTransitRequest(TRANSIT_OPEN)) {
-            if (openingType == TYPE_TASK) {
-                return (appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0
-                        ? TRANSIT_OLD_TASK_OPEN_BEHIND : TRANSIT_OLD_TASK_OPEN;
-            }
-            if (openingType == TYPE_ACTIVITY) {
-                return TRANSIT_OLD_ACTIVITY_OPEN;
-            }
-            if (openingType == TYPE_TASK_FRAGMENT) {
-                return TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-            }
-        }
-        if (appTransition.containsTransitRequest(TRANSIT_CLOSE)) {
-            if (closingType == TYPE_TASK) {
-                return TRANSIT_OLD_TASK_CLOSE;
-            }
-            if (closingType == TYPE_TASK_FRAGMENT) {
-                return TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-            }
-            if (closingType == TYPE_ACTIVITY) {
-                for (int i = closingApps.size() - 1; i >= 0; i--) {
-                    if (closingApps.valueAt(i).visibleIgnoringKeyguard) {
-                        return TRANSIT_OLD_ACTIVITY_CLOSE;
-                    }
-                }
-                // Skip close activity transition since no closing app can be visible
-                return WindowManager.TRANSIT_OLD_UNSET;
-            }
-        }
-        if (appTransition.containsTransitRequest(TRANSIT_RELAUNCH)
-                && !openingWcs.isEmpty() && !openingApps.isEmpty()) {
-            return TRANSIT_OLD_ACTIVITY_RELAUNCH;
-        }
-        return TRANSIT_OLD_NONE;
-    }
-
-    @TransitContainerType
-    private static int getTransitContainerType(@Nullable WindowContainer<?> container) {
-        if (container == null) {
-            return TYPE_NONE;
-        }
-        if (container.asTask() != null) {
-            return TYPE_TASK;
-        }
-        if (container.asTaskFragment() != null) {
-            return TYPE_TASK_FRAGMENT;
-        }
-        if (container.asActivityRecord() != null) {
-            return TYPE_ACTIVITY;
-        }
-        return TYPE_NONE;
-    }
-
-    @Nullable
-    private static WindowManager.LayoutParams getAnimLp(ActivityRecord activity) {
-        final WindowState mainWindow = activity != null ? activity.findMainWindow() : null;
-        return mainWindow != null ? mainWindow.mAttrs : null;
-    }
-
-    RemoteAnimationAdapter getRemoteAnimationOverride(@Nullable WindowContainer container,
-            @TransitionOldType int transit, ArraySet<Integer> activityTypes) {
-        if (container != null) {
-            final RemoteAnimationDefinition definition = container.getRemoteAnimationDefinition();
-            if (definition != null) {
-                final RemoteAnimationAdapter adapter = definition.getAdapter(transit,
-                        activityTypes);
-                if (adapter != null) {
-                    return adapter;
-                }
-            }
-        }
-        return mRemoteAnimationDefinition != null
-                ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
-                : null;
-    }
-
-    private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
-        // We don't want to have the client to animate any non-app windows.
-        // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
-        // non-app windows will only be included with those transition types. And we don't currently
-        // have any use case of those for TaskFragment transition.
-        return shouldStartNonAppWindowAnimationsForKeyguardExit(transit)
-                || shouldAttachNavBarToApp(mService, mDisplayContent, transit)
-                || shouldStartWallpaperAnimation(mDisplayContent);
-    }
-
-    /**
-     * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
-     * parent {@link Task} before or after the transition.
-     */
-    private boolean transitionContainsTaskFragmentWithBoundsOverride() {
-        for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
-            final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
-            if (wc.isEmbedded()) {
-                // Contains embedded TaskFragment with bounds changed.
-                return true;
-            }
-        }
-        mTempTransitionWindows.clear();
-        mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
-        mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
-        boolean containsTaskFragmentWithBoundsOverride = false;
-        for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
-            final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
-            final TaskFragment tf = r.getTaskFragment();
-            if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
-                containsTaskFragmentWithBoundsOverride = true;
-                break;
-            }
-        }
-        mTempTransitionWindows.clear();
-        return containsTaskFragmentWithBoundsOverride;
-    }
-
-    /**
-     * Finds the common parent {@link Task} that is parent of all embedded app windows in the
-     * current transition.
-     * @return {@code null} if app windows in the transition are not children of the same Task, or
-     *         if none of the app windows is embedded.
-     */
-    @Nullable
-    private Task findParentTaskForAllEmbeddedWindows() {
-        mTempTransitionWindows.clear();
-        mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
-        mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
-        mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
-
-        // It should only animated by the organizer if all windows are below the same leaf Task.
-        Task leafTask = null;
-        for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
-            final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
-            if (r == null) {
-                leafTask = null;
-                break;
-            }
-            // There are also cases where the Task contains non-embedded activity, such as launching
-            // split TaskFragments from a non-embedded activity.
-            // The hierarchy may looks like this:
-            // - Task
-            //    - Activity
-            //    - TaskFragment
-            //       - Activity
-            //    - TaskFragment
-            //       - Activity
-            // We also want to have the organizer handle the transition for such case.
-            final Task task = r.getTask();
-            // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
-            if (task == null || task.inPinnedWindowingMode()) {
-                leafTask = null;
-                break;
-            }
-            // We don't want the organizer to handle transition of other non-embedded Task.
-            if (leafTask != null && leafTask != task) {
-                leafTask = null;
-                break;
-            }
-            final ActivityRecord rootActivity = task.getRootActivity();
-            // We don't want the organizer to handle transition when the whole app is closing.
-            if (rootActivity == null) {
-                leafTask = null;
-                break;
-            }
-            // We don't want the organizer to handle transition of non-embedded activity of other
-            // app.
-            if (r.getUid() != task.effectiveUid && !r.isEmbedded()) {
-                leafTask = null;
-                break;
-            }
-            leafTask = task;
-        }
-        mTempTransitionWindows.clear();
-        return leafTask;
-    }
-
-    /**
-     * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all embedded
-     * {@link TaskFragment} belong to the given {@link Task}.
-     * @return {@code null} if there is no such organizer, or if there are more than one.
-     */
-    @Nullable
-    private ITaskFragmentOrganizer findTaskFragmentOrganizer(@Nullable Task task) {
-        if (task == null) {
-            return null;
-        }
-        // We don't support remote animation for Task with multiple TaskFragmentOrganizers.
-        final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1];
-        final boolean hasMultipleOrganizers = task.forAllLeafTaskFragments(taskFragment -> {
-            final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer();
-            if (tfOrganizer == null) {
-                return false;
-            }
-            if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) {
-                return true;
-            }
-            organizer[0] = tfOrganizer;
-            return false;
-        });
-        if (hasMultipleOrganizers) {
-            ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
-                    + " Task with multiple TaskFragmentOrganizers.");
-            return null;
-        }
-        return organizer[0];
-    }
-
-    /**
-     * Overrides the pending transition with the remote animation defined by the
-     * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
-     * {@link TaskFragment} that are organized by the same organizer.
-     *
-     * @return {@code true} if the transition is overridden.
-     */
-    private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
-            ArraySet<Integer> activityTypes) {
-        if (transitionMayContainNonAppWindows(transit)) {
-            return false;
-        }
-        if (!transitionContainsTaskFragmentWithBoundsOverride()) {
-            // No need to play TaskFragment remote animation if all embedded TaskFragment in the
-            // transition fill the Task.
-            return false;
-        }
-
-        final Task task = findParentTaskForAllEmbeddedWindows();
-        final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
-        final RemoteAnimationDefinition definition = organizer != null
-                ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                    .getRemoteAnimationDefinition(organizer)
-                : null;
-        final RemoteAnimationAdapter adapter = definition != null
-                ? definition.getAdapter(transit, activityTypes)
-                : null;
-        if (adapter == null) {
-            return false;
-        }
-        mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
-                adapter, false /* sync */, true /*isActivityEmbedding*/);
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "Override with TaskFragment remote animation for transit=%s",
-                AppTransition.appTransitionOldToString(transit));
-
-        final int organizerUid = mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                .getTaskFragmentOrganizerUid(organizer);
-        final boolean shouldDisableInputForRemoteAnimation = !task.isFullyTrustedEmbedding(
-                organizerUid);
-        final RemoteAnimationController remoteAnimationController =
-                mDisplayContent.mAppTransition.getRemoteAnimationController();
-        if (shouldDisableInputForRemoteAnimation && remoteAnimationController != null) {
-            // We are going to use client-driven animation, Disable all input on activity windows
-            // during the animation (unless it is fully trusted) to ensure it is safe to allow
-            // client to animate the surfaces.
-            // This is needed for all activity windows in the animation Task.
-            remoteAnimationController.setOnRemoteAnimationReady(() -> {
-                final Consumer<ActivityRecord> updateActivities =
-                        activity -> activity.setDropInputForAnimation(true);
-                task.forAllActivities(updateActivities);
-            });
-            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment."
-                    + " Disabled all input during TaskFragment remote animation.", task.mTaskId);
-        }
-        return true;
-    }
-
-    /**
-     * Overrides the pending transition with the remote animation defined for the transition in the
-     * set of defined remote animations in the app window token.
-     */
-    private void overrideWithRemoteAnimationIfSet(@Nullable ActivityRecord animLpActivity,
-            @TransitionOldType int transit, ArraySet<Integer> activityTypes) {
-        RemoteAnimationAdapter adapter = null;
-        if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
-            // The crash transition has higher priority than any involved remote animations.
-        } else if (AppTransition.isKeyguardGoingAwayTransitOld(transit)) {
-            adapter = mRemoteAnimationDefinition != null
-                    ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
-                    : null;
-        } else if (mDisplayContent.mAppTransition.getRemoteAnimationController() == null) {
-            adapter = getRemoteAnimationOverride(animLpActivity, transit, activityTypes);
-        }
-        if (adapter != null) {
-            mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
-        }
-    }
-
-    @Nullable
-    static Task findRootTaskFromContainer(WindowContainer wc) {
-        return wc.asTaskFragment() != null ? wc.asTaskFragment().getRootTask()
-                : wc.asActivityRecord().getRootTask();
-    }
-
-    @Nullable
-    static ActivityRecord getAppFromContainer(WindowContainer wc) {
-        return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
-                : wc.asActivityRecord();
-    }
-
-    /**
-     * @return The window token that determines the animation theme.
-     */
-    @Nullable
-    private ActivityRecord findAnimLayoutParamsToken(@TransitionOldType int transit,
-            ArraySet<Integer> activityTypes, ArraySet<ActivityRecord> openingApps,
-            ArraySet<ActivityRecord> closingApps, ArraySet<WindowContainer> changingApps) {
-        ActivityRecord result;
-
-        // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
-        result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
-                w -> w.getRemoteAnimationDefinition() != null
-                        && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
-        if (result != null) {
-            return result;
-        }
-        result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
-                w -> w.fillsParent() && w.findMainWindow() != null);
-        if (result != null) {
-            return result;
-        }
-        return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
-                w -> w.findMainWindow() != null);
-    }
-
-    /**
-     * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
-     *         of apps in {@code array1}, {@code array2}, and {@code array3}.
-     */
-    private static ArraySet<Integer> collectActivityTypes(ArraySet<ActivityRecord> array1,
-            ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3) {
-        final ArraySet<Integer> result = new ArraySet<>();
-        for (int i = array1.size() - 1; i >= 0; i--) {
-            result.add(array1.valueAt(i).getActivityType());
-        }
-        for (int i = array2.size() - 1; i >= 0; i--) {
-            result.add(array2.valueAt(i).getActivityType());
-        }
-        for (int i = array3.size() - 1; i >= 0; i--) {
-            result.add(array3.valueAt(i).getActivityType());
-        }
-        return result;
-    }
-
-    private static ActivityRecord lookForHighestTokenWithFilter(ArraySet<ActivityRecord> array1,
-            ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3,
-            Predicate<ActivityRecord> filter) {
-        final int array2base = array1.size();
-        final int array3base = array2.size() + array2base;
-        final int count = array3base + array3.size();
-        int bestPrefixOrderIndex = Integer.MIN_VALUE;
-        ActivityRecord bestToken = null;
-        for (int i = 0; i < count; i++) {
-            final WindowContainer wtoken = i < array2base
-                    ? array1.valueAt(i)
-                    : (i < array3base
-                            ? array2.valueAt(i - array2base)
-                            : array3.valueAt(i - array3base));
-            final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
-            final ActivityRecord r = getAppFromContainer(wtoken);
-            if (r != null && filter.test(r) && prefixOrderIndex > bestPrefixOrderIndex) {
-                bestPrefixOrderIndex = prefixOrderIndex;
-                bestToken = r;
-            }
-        }
-        return bestToken;
-    }
-
-    private boolean containsVoiceInteraction(ArraySet<ActivityRecord> apps) {
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            if (apps.valueAt(i).mVoiceInteraction) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Apply animation to the set of window containers.
-     *
-     * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
-     * @param apps The list of {@link ActivityRecord}s being transitioning.
-     * @param transit The current transition type.
-     * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
-     *                invisible.
-     * @param animLp Layout parameters in which an app transition animation runs.
-     * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
-     *                         interaction session driving task.
-     */
-    private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
-            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
-            boolean voiceInteraction) {
-        final int wcsCount = wcs.size();
-        for (int i = 0; i < wcsCount; i++) {
-            final WindowContainer wc = wcs.valueAt(i);
-            // If app transition animation target is promoted to higher level, SurfaceAnimator
-            // triggers WC#onAnimationFinished only on the promoted target. So we need to take care
-            // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
-            // app transition.
-            final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
-            for (int j = 0; j < apps.size(); ++j) {
-                final ActivityRecord app = apps.valueAt(j);
-                if (app.isDescendantOf(wc)) {
-                    transitioningDescendants.add(app);
-                }
-            }
-            wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
-        }
-    }
-
-    /**
-     * Returns {@code true} if a given {@link WindowContainer} is an embedded Task in
-     * {@link TaskView}.
-     *
-     * Note that this is a short term workaround to support Android Auto until it migrate to
-     * ShellTransition. This should only be used by {@link #getAnimationTargets}.
-     *
-     * TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled.
-     */
-    static boolean isTaskViewTask(WindowContainer wc) {
-        // Use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and
-        // it is not guaranteed to work this logic in the future version.
-        boolean isTaskViewTask =  wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer;
-        if (isTaskViewTask) {
-            return true;
-        }
-
-        WindowContainer parent = wc.getParent();
-        boolean isParentATaskViewTask = parent != null
-                && parent instanceof Task
-                && ((Task) parent).mRemoveWithTaskOrganizer;
-        return isParentATaskViewTask;
-    }
-
-    /**
-     * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
-     * animation targets to higher level in the window hierarchy if possible.
-     *
-     * @param visible {@code true} to get animation targets for opening apps, {@code false} to get
-     *                            animation targets for closing apps.
-     * @return {@link WindowContainer}s to be animated.
-     */
-    @VisibleForTesting
-    static ArraySet<WindowContainer> getAnimationTargets(
-            ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
-            boolean visible) {
-
-        // The candidates of animation targets, which might be able to promote to higher level.
-        final ArrayDeque<WindowContainer> candidates = new ArrayDeque<>();
-        final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
-        for (int i = 0; i < apps.size(); ++i) {
-            final ActivityRecord app = apps.valueAt(i);
-            if (app.shouldApplyAnimation(visible)) {
-                candidates.add(app);
-                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                        "Changing app %s visible=%b performLayout=%b",
-                        app, app.isVisible(), false);
-            }
-        }
-
-        final ArraySet<ActivityRecord> otherApps = visible ? closingApps : openingApps;
-        // Ancestors of closing apps while finding animation targets for opening apps, or ancestors
-        // of opening apps while finding animation targets for closing apps.
-        final ArraySet<WindowContainer> otherAncestors = new ArraySet<>();
-        for (int i = 0; i < otherApps.size(); ++i) {
-            for (WindowContainer wc = otherApps.valueAt(i); wc != null; wc = wc.getParent()) {
-                otherAncestors.add(wc);
-            }
-        }
-
-        // The final animation targets which cannot promote to higher level anymore.
-        final ArraySet<WindowContainer> targets = new ArraySet<>();
-        final ArrayList<WindowContainer> siblings = new ArrayList<>();
-        while (!candidates.isEmpty()) {
-            final WindowContainer current = candidates.removeFirst();
-            final WindowContainer parent = current.getParent();
-            siblings.clear();
-            siblings.add(current);
-            boolean canPromote = true;
-
-            if (isTaskViewTask(current)) {
-                // Don't animate an embedded Task in app transition. This is a short term workaround
-                // to prevent conflict of surface hierarchy changes between legacy app transition
-                // and TaskView (b/205189147).
-                // TODO(b/213312721): Remove this once ShellTransition is enabled.
-                continue;
-            } else if (parent == null || !parent.canCreateRemoteAnimationTarget()
-                    // We cannot promote the animation on Task's parent when the task is in
-                    // clearing task in case the animating get stuck when performing the opening
-                    // task that behind it.
-                    || (current.asTask() != null && current.asTask().mInRemoveTask)
-                    // We cannot promote the animation to changing window. This may happen when an
-                    // activity is open in a TaskFragment that is resizing, while the existing
-                    // activity in the TaskFragment is reparented to another TaskFragment.
-                    || parent.isChangingAppTransition()) {
-                canPromote = false;
-            } else {
-                // In case a descendant of the parent belongs to the other group, we cannot promote
-                // the animation target from "current" to the parent.
-                //
-                // Example: Imagine we're checking if we can animate a Task instead of a set of
-                // ActivityRecords. In case an activity starts a new activity within a same Task,
-                // an ActivityRecord of an existing activity belongs to the opening apps, at the
-                // same time, the other ActivityRecord of a new activity belongs to the closing
-                // apps. In this case, we cannot promote the animation target to Task level, but
-                // need to animate each individual activity.
-                //
-                // [Task] +- [ActivityRecord1] (in opening apps)
-                //        +- [ActivityRecord2] (in closing apps)
-                if (otherAncestors.contains(parent)) {
-                    canPromote = false;
-                }
-
-                // If the current window container is a task with adjacent task set, the both
-                // adjacent tasks will be opened or closed together. To get their opening or
-                // closing animation target independently, skip promoting their animation targets.
-                if (current.asTask() != null && current.asTask().hasAdjacentTask()) {
-                    canPromote = false;
-                }
-
-                // Find all siblings of the current WindowContainer in "candidates", move them into
-                // a separate list "siblings", and checks if an animation target can be promoted
-                // to its parent.
-                //
-                // We can promote an animation target to its parent if and only if all visible
-                // siblings will be animating.
-                //
-                // Example: Imagine that a Task contains two visible activity record, but only one
-                // of them is included in the opening apps and the other belongs to neither opening
-                // or closing apps. This happens when an activity launches another translucent
-                // activity in the same Task. In this case, we cannot animate Task, but have to
-                // animate each activity, otherwise an activity behind the translucent activity also
-                // animates.
-                //
-                // [Task] +- [ActivityRecord1] (visible, in opening apps)
-                //        +- [ActivityRecord2] (visible, not in opening apps)
-                for (int j = 0; j < parent.getChildCount(); ++j) {
-                    final WindowContainer sibling = parent.getChildAt(j);
-                    if (candidates.remove(sibling)) {
-                        if (!isTaskViewTask(sibling)) {
-                            // Don't animate an embedded Task in app transition. This is a short
-                            // term workaround to prevent conflict of surface hierarchy changes
-                            // between legacy app transition and TaskView (b/205189147).
-                            // TODO(b/213312721): Remove this once ShellTransition is enabled.
-                            siblings.add(sibling);
-                        }
-                    } else if (sibling != current && sibling.isVisible()) {
-                        canPromote = false;
-                    }
-                }
-            }
-
-            if (canPromote) {
-                candidates.add(parent);
-            } else {
-                targets.addAll(siblings);
-            }
-        }
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
-                apps, targets);
-        return targets;
-    }
-
-    /**
-     * Apply an app transition animation based on a set of {@link ActivityRecord}
-     *
-     * @param openingApps The list of opening apps to which an app transition animation applies.
-     * @param closingApps The list of closing apps to which an app transition animation applies.
-     * @param transit The current transition type.
-     * @param animLp Layout parameters in which an app transition animation runs.
-     * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
-     *                         interaction session driving task.
-     */
-    private void applyAnimations(ArraySet<ActivityRecord> openingApps,
-            ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
-            LayoutParams animLp, boolean voiceInteraction) {
-        if (transit == WindowManager.TRANSIT_OLD_UNSET
-                || (openingApps.isEmpty() && closingApps.isEmpty())) {
-            return;
-        }
-
-        if (AppTransition.isActivityTransitOld(transit)) {
-            final ArrayList<Pair<ActivityRecord, Rect>> closingLetterboxes = new ArrayList();
-            for (int i = 0; i < closingApps.size(); ++i) {
-                ActivityRecord closingApp = closingApps.valueAt(i);
-                if (closingApp.areBoundsLetterboxed()) {
-                    final Rect insets = closingApp.getLetterboxInsets();
-                    closingLetterboxes.add(new Pair(closingApp, insets));
-                }
-            }
-
-            for (int i = 0; i < openingApps.size(); ++i) {
-                ActivityRecord openingApp = openingApps.valueAt(i);
-                if (openingApp.areBoundsLetterboxed()) {
-                    final Rect openingInsets = openingApp.getLetterboxInsets();
-                    for (Pair<ActivityRecord, Rect> closingLetterbox : closingLetterboxes) {
-                        final Rect closingInsets = closingLetterbox.second;
-                        if (openingInsets.equals(closingInsets)) {
-                            ActivityRecord closingApp = closingLetterbox.first;
-                            openingApp.setNeedsLetterboxedAnimation(true);
-                            closingApp.setNeedsLetterboxedAnimation(true);
-                        }
-                    }
-                }
-            }
-        }
-
-        final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
-                openingApps, closingApps, true /* visible */);
-        final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
-                openingApps, closingApps, false /* visible */);
-        applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
-                voiceInteraction);
-        applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
-                voiceInteraction);
-
-        for (int i = 0; i < openingApps.size(); ++i) {
-            openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
-        }
-        for (int i = 0; i < closingApps.size(); ++i) {
-            closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
-        }
-
-        final AccessibilityController accessibilityController =
-                mDisplayContent.mWmService.mAccessibilityController;
-        if (accessibilityController.hasCallbacks()) {
-            accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
-        }
-    }
-
-    private void handleOpeningApps() {
-        final ArraySet<ActivityRecord> openingApps = mDisplayContent.mOpeningApps;
-        final int appsCount = openingApps.size();
-
-        for (int i = 0; i < appsCount; i++) {
-            final ActivityRecord app = openingApps.valueAt(i);
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now opening app %s", app);
-
-            app.commitVisibility(true /* visible */, false /* performLayout */);
-
-            // In case a trampoline activity is used, it can happen that a new ActivityRecord is
-            // added and a new app transition starts before the previous app transition animation
-            // ends. So we cannot simply use app.isAnimating(PARENTS) to determine if the app must
-            // to be added to the list of tokens to be notified of app transition complete.
-            final WindowContainer wc = app.getAnimatingContainer(PARENTS,
-                    ANIMATION_TYPE_APP_TRANSITION);
-            if (wc == null || !wc.getAnimationSources().contains(app)) {
-                // This token isn't going to be animating. Add it to the list of tokens to
-                // be notified of app transition complete since the notification will not be
-                // sent be the app window animator.
-                mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(app.token);
-            }
-            app.updateReportedVisibilityLocked();
-            app.showAllWindowsLocked();
-
-            if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                app.attachThumbnailAnimation();
-            } else if (mDisplayContent.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
-                app.attachCrossProfileAppsThumbnailAnimation();
-            }
-        }
-    }
-
-    private void handleClosingApps() {
-        final ArraySet<ActivityRecord> closingApps = mDisplayContent.mClosingApps;
-        final int appsCount = closingApps.size();
-
-        for (int i = 0; i < appsCount; i++) {
-            final ActivityRecord app = closingApps.valueAt(i);
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now closing app %s", app);
-
-            app.commitVisibility(false /* visible */, false /* performLayout */);
-            app.updateReportedVisibilityLocked();
-            // Force the allDrawn flag, because we want to start
-            // this guy's animations regardless of whether it's
-            // gotten drawn.
-            app.allDrawn = true;
-            // Ensure that apps that are mid-starting are also scheduled to have their
-            // starting windows removed after the animation is complete
-            if (app.mStartingWindow != null && !app.mStartingWindow.mAnimatingExit) {
-                app.removeStartingWindow();
-            }
-
-            if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                app.attachThumbnailAnimation();
-            }
-        }
-    }
-
-    private void handleClosingChangingContainers() {
-        final ArrayMap<WindowContainer, Rect> containers =
-                mDisplayContent.mClosingChangingContainers;
-        while (!containers.isEmpty()) {
-            final WindowContainer container = containers.keyAt(0);
-            containers.remove(container);
-
-            // For closing changing windows that are part of the transition, they should have been
-            // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter()
-            // If the closing changing TaskFragment is not part of the transition, update its
-            // surface after removing it from mClosingChangingContainers.
-            final TaskFragment taskFragment = container.asTaskFragment();
-            if (taskFragment != null) {
-                taskFragment.updateOrganizedTaskFragmentSurface();
-            }
-        }
-    }
-
-    private void handleChangingApps(@TransitionOldType int transit) {
-        final ArraySet<WindowContainer> apps = mDisplayContent.mChangingContainers;
-        final int appsCount = apps.size();
-        for (int i = 0; i < appsCount; i++) {
-            WindowContainer wc = apps.valueAt(i);
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", wc);
-            wc.applyAnimation(null, transit, true, false, null /* sources */);
-        }
-    }
-
-    private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
-            ArrayMap<WindowContainer, Integer> outReasons) {
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "Checking %d opening apps (timeout=%b)...", apps.size(),
-                mDisplayContent.mAppTransition.isTimeout());
-        if (mDisplayContent.mAppTransition.isTimeout()) {
-            return true;
-        }
-
-        for (int i = 0; i < apps.size(); i++) {
-            WindowContainer wc = apps.valueAt(i);
-            final ActivityRecord activity = getAppFromContainer(wc);
-            if (activity == null) {
-                continue;
-            }
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                    "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
-                            + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
-                    activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
-                    activity.startingMoved, activity.isRelaunching(),
-                    activity.mStartingWindow);
-            final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
-            if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
-                return false;
-            }
-            if (allDrawn) {
-                outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN);
-            } else {
-                outReasons.put(activity,
-                        activity.mStartingData instanceof SplashScreenStartingData
-                                ? APP_TRANSITION_SPLASH_SCREEN
-                                : APP_TRANSITION_SNAPSHOT);
-            }
-        }
-
-        // We also need to wait for the specs to be fetched, if needed.
-        if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true");
-            return false;
-        }
-
-        if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s",
-                    mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
-            return false;
-        }
-
-        // If the wallpaper is visible, we need to check it's ready too.
-        return !mWallpaperControllerLocked.isWallpaperVisible()
-                || mWallpaperControllerLocked.wallpaperTransitionReady();
-    }
-
-    private boolean transitionGoodToGoForTaskFragments() {
-        if (mDisplayContent.mAppTransition.isTimeout()) {
-            return true;
-        }
-
-        // Check all Tasks in this transition. This is needed because new TaskFragment created for
-        // launching activity may not be in the tracking lists, but we still want to wait for the
-        // activity launch to start the transition.
-        final ArraySet<Task> rootTasks = new ArraySet<>();
-        for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) {
-            rootTasks.add(mDisplayContent.mOpeningApps.valueAt(i).getRootTask());
-        }
-        for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) {
-            rootTasks.add(mDisplayContent.mClosingApps.valueAt(i).getRootTask());
-        }
-        for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
-            rootTasks.add(
-                    findRootTaskFromContainer(mDisplayContent.mChangingContainers.valueAt(i)));
-        }
-
-        // Organized TaskFragment can be empty for two situations:
-        // 1. New created and is waiting for Activity launch. In this case, we want to wait for
-        //    the Activity launch to trigger the transition.
-        // 2. Last Activity is just removed. In this case, we want to wait for organizer to
-        //    remove the TaskFragment because it may also want to change other TaskFragments in
-        //    the same transition.
-        for (int i = rootTasks.size() - 1; i >= 0; i--) {
-            final Task rootTask = rootTasks.valueAt(i);
-            if (rootTask == null) {
-                // It is possible that one activity may have been removed from the hierarchy. No
-                // need to check for this case.
-                continue;
-            }
-            final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> {
-                if (!taskFragment.isReadyToTransit()) {
-                    ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s",
-                            taskFragment);
-                    return true;
-                }
-                return false;
-            });
-            if (notReady) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Identifies whether the current transition occurs within a single task or not. This is used
-     * to determine whether animations should be clipped to the task bounds instead of root task
-     * bounds.
-     */
-    @VisibleForTesting
-    boolean isTransitWithinTask(@TransitionOldType int transit, Task task) {
-        if (task == null
-                || !mDisplayContent.mChangingContainers.isEmpty()) {
-            // if there is no task, then we can't constrain to the task.
-            // if anything is changing, it can animate outside its task.
-            return false;
-        }
-        if (!(transit == TRANSIT_OLD_ACTIVITY_OPEN
-                || transit == TRANSIT_OLD_ACTIVITY_CLOSE
-                || transit == TRANSIT_OLD_ACTIVITY_RELAUNCH)) {
-            // only activity-level transitions will be within-task.
-            return false;
-        }
-        // check that all components are in the task.
-        for (ActivityRecord activity : mDisplayContent.mOpeningApps) {
-            Task activityTask = activity.getTask();
-            if (activityTask != task) {
-                return false;
-            }
-        }
-        for (ActivityRecord activity : mDisplayContent.mClosingApps) {
-            if (activity.getTask() != task) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean canBeWallpaperTarget(ArraySet<ActivityRecord> apps) {
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            if (apps.valueAt(i).windowsCanBeWallpaperTarget()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Finds the top app in a list of apps, using its {@link ActivityRecord#getPrefixOrderIndex} to
-     * compare z-order.
-     *
-     * @param apps The list of apps to search.
-     * @param ignoreInvisible If set to true, ignores apps that are not
-     *                        {@link ActivityRecord#isVisible}.
-     * @return The top {@link ActivityRecord}.
-     */
-    private static ActivityRecord getTopApp(ArraySet<? extends WindowContainer> apps,
-            boolean ignoreInvisible) {
-        int topPrefixOrderIndex = Integer.MIN_VALUE;
-        ActivityRecord topApp = null;
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            final ActivityRecord app = getAppFromContainer(apps.valueAt(i));
-            if (app == null || ignoreInvisible && !app.isVisible()) {
-                continue;
-            }
-            final int prefixOrderIndex = app.getPrefixOrderIndex();
-            if (prefixOrderIndex > topPrefixOrderIndex) {
-                topPrefixOrderIndex = prefixOrderIndex;
-                topApp = app;
-            }
-        }
-        return topApp;
-    }
-}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bf54c73..682f3d8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -226,7 +226,6 @@
 import android.view.InsetsState;
 import android.view.MagnificationSpec;
 import android.view.PrivacyIndicatorBounds;
-import android.view.RemoteAnimationDefinition;
 import android.view.RoundedCorners;
 import android.view.Surface;
 import android.view.Surface.Rotation;
@@ -367,8 +366,6 @@
     private int mMaxUiWidth = 0;
 
     final AppTransition mAppTransition;
-    final AppTransitionController mAppTransitionController;
-    boolean mSkipAppTransitionAnimation = false;
 
     final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
     final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
@@ -547,9 +544,6 @@
     // TODO(multi-display): remove some of the usages.
     boolean isDefaultDisplay;
 
-    /** Indicates whether any presentation is shown on this display. */
-    boolean mIsPresenting;
-
     /** Save allocating when calculating rects */
     private final Rect mTmpRect = new Rect();
     private final Region mTmpRegion = new Region();
@@ -1164,7 +1158,6 @@
         mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
         mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
         mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
-        mAppTransitionController = new AppTransitionController(mWmService, this);
         mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
         mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
         mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -1556,10 +1549,6 @@
         return mInputMethodSurfaceParentWindow;
     }
 
-    void registerRemoteAnimations(RemoteAnimationDefinition definition) {
-        mAppTransitionController.registerRemoteAnimations(definition);
-    }
-
     void reconfigureDisplayLocked() {
         if (!isReady()) {
             return;
@@ -5607,20 +5596,6 @@
     }
 
     /**
-     * Transfer app transition from other display to this display.
-     *
-     * @param from Display from where the app transition is transferred.
-     *
-     * TODO(new-app-transition): Remove this once the shell handles app transition.
-     */
-    void transferAppTransitionFrom(DisplayContent from) {
-        final boolean prepared = mAppTransition.transferFrom(from.mAppTransition);
-        if (prepared && okToAnimate()) {
-            mSkipAppTransitionAnimation = false;
-        }
-    }
-
-    /**
      * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
      */
     @Deprecated
@@ -5634,10 +5609,7 @@
     @Deprecated
     void prepareAppTransition(@WindowManager.TransitionType int transit,
             @WindowManager.TransitionFlags int flags) {
-        final boolean prepared = mAppTransition.prepareAppTransition(transit, flags);
-        if (prepared && okToAnimate() && transit != TRANSIT_NONE) {
-            mSkipAppTransitionAnimation = false;
-        }
+        mAppTransition.prepareAppTransition(transit, flags);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
new file mode 100644
index 0000000..6946343
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
+
+import android.annotation.NonNull;
+import android.util.IntArray;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.WmProtoLogGroups;
+
+/**
+ * Manages presentation windows.
+ */
+class PresentationController {
+
+    // TODO(b/395475549): Add support for display add/remove, and activity move across displays.
+    private final IntArray mPresentingDisplayIds = new IntArray();
+
+    PresentationController() {}
+
+    private boolean isPresenting(int displayId) {
+        return mPresentingDisplayIds.contains(displayId);
+    }
+
+    boolean shouldOccludeActivities(int displayId) {
+        // All activities on the presenting display must be hidden so that malicious apps can't do
+        // tap jacking (b/391466268).
+        // For now, this should only be applied to external displays because presentations can only
+        // be shown on them.
+        // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
+        // the presentation won't stop its controlling activity.
+        return enablePresentationForConnectedDisplays() && isPresenting(displayId);
+    }
+
+    void onPresentationAdded(@NonNull WindowState win) {
+        final int displayId = win.getDisplayId();
+        if (isPresenting(displayId)) {
+            return;
+        }
+        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
+                win.getDisplayId(), win);
+        mPresentingDisplayIds.add(win.getDisplayId());
+        if (enablePresentationForConnectedDisplays()) {
+            // A presentation hides all activities behind on the same display.
+            win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+                    /*notifyClients=*/ true);
+        }
+        win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
+    }
+
+    void onPresentationRemoved(@NonNull WindowState win) {
+        final int displayId = win.getDisplayId();
+        if (!isPresenting(displayId)) {
+            return;
+        }
+        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION,
+                "Presentation removed from display %d: %s", win.getDisplayId(), win);
+        // TODO(b/393945496): Make sure that there's one presentation at most per display.
+        final int displayIdIndex = mPresentingDisplayIds.indexOf(displayId);
+        if (displayIdIndex != -1) {
+            mPresentingDisplayIds.remove(displayIdIndex);
+        }
+        if (enablePresentationForConnectedDisplays()) {
+            // A presentation hides all activities behind on the same display.
+            win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+                    /*notifyClients=*/ true);
+        }
+        win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 95d9b3e..c93efd3 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -35,7 +35,6 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_SLEEP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_WAKE;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
@@ -68,7 +67,6 @@
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
-import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -803,8 +801,6 @@
         mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
         mWmService.mSyncEngine.onSurfacePlacement();
 
-        checkAppTransitionReady(surfacePlacer);
-
         mWmService.mAtmService.mBackNavigationController
                 .checkAnimationReady(defaultDisplay.mWallpaperController);
 
@@ -898,38 +894,6 @@
         if (DEBUG_WINDOW_TRACE) Slog.e(TAG, "performSurfacePlacementInner exit");
     }
 
-    private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
-        // Trace all displays app transition by Z-order for pending layout change.
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final DisplayContent curDisplay = mChildren.get(i);
-
-            // If we are ready to perform an app transition, check through all of the app tokens
-            // to be shown and see if they are ready to go.
-            if (curDisplay.mAppTransition.isReady()) {
-                // handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
-                curDisplay.mAppTransitionController.handleAppTransitionReady();
-                if (DEBUG_LAYOUT_REPEATS) {
-                    surfacePlacer.debugLayoutRepeats("after handleAppTransitionReady",
-                            curDisplay.pendingLayoutChanges);
-                }
-            }
-
-            if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) {
-                // We have finished the animation of an app transition. To do this, we have
-                // delayed a lot of operations like showing and hiding apps, moving apps in
-                // Z-order, etc.
-                // The app token list reflects the correct Z-order, but the window list may now
-                // be out of sync with it. So here we will just rebuild the entire app window
-                // list. Fun!
-                curDisplay.handleAnimatingStoppedAndTransition();
-                if (DEBUG_LAYOUT_REPEATS) {
-                    surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
-                            curDisplay.pendingLayoutChanges);
-                }
-            }
-        }
-    }
-
     private void applySurfaceChangesTransaction() {
         // TODO(multi-display): Support these features on secondary screens.
         final DisplayContent defaultDc = mDefaultDisplay;
@@ -2266,20 +2230,6 @@
 
                 // Ensure the leash of new task is in sync with its current bounds after reparent.
                 rootTask.maybeApplyLastRecentsAnimationTransaction();
-
-                // In the case of this activity entering PIP due to it being moved to the back,
-                // the old activity would have a TRANSIT_TASK_TO_BACK transition that needs to be
-                // ran. But, since its visibility did not change (note how it was STOPPED/not
-                // visible, and with it now at the back stack, it remains not visible), the logic to
-                // add the transition is automatically skipped. We then add this activity manually
-                // to the list of apps being closed, and request its transition to be ran.
-                final ActivityRecord oldTopActivity = task.getTopMostActivity();
-                if (oldTopActivity != null && oldTopActivity.isState(STOPPED)
-                        && task.getDisplayContent().mAppTransition.containsTransitRequest(
-                        TRANSIT_TO_BACK)) {
-                    task.getDisplayContent().mClosingApps.add(oldTopActivity);
-                    oldTopActivity.mRequestForceTransition = true;
-                }
             }
 
             // TODO(remove-legacy-transit): Move this to the `singleActivity` case when removing
@@ -2958,20 +2908,6 @@
         display.mAllSleepTokens.remove(token);
         if (display.mAllSleepTokens.isEmpty()) {
             mService.updateSleepIfNeededLocked();
-            // Assuming no lock screen is set and a user launches an activity, turns off the screen
-            // and turn on the screen again, then the launched activity should be displayed on the
-            // screen without app transition animation. When the screen turns on, both keyguard
-            // sleep token and display off sleep token are removed, but the order is
-            // non-deterministic.
-            // Note: Display#mSkipAppTransitionAnimation will be ignored when keyguard related
-            // transition exists, so this affects only when no lock screen is set. Otherwise
-            // keyguard going away animation will be played.
-            // See also AppTransitionController#getTransitCompatType for more details.
-            if ((!mTaskSupervisor.getKeyguardController().isKeyguardOccluded(display.mDisplayId)
-                    && token.mTag.equals(KEYGUARD_SLEEP_TOKEN_TAG))
-                    || token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) {
-                display.mSkipAppTransitionAnimation = true;
-            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index dcdffa4..2664dcd 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -72,11 +72,6 @@
         mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
     }
 
-    // For legacy transition, which won't support activity snapshot
-    void onTransitionStarting(DisplayContent displayContent) {
-        mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
-    }
-
     // For shell transition, record snapshots before transaction start.
     void onTransactionReady(@WindowManager.TransitionType int type,
             ArrayList<Transition.ChangeInfo> changeInfos) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3dfff39..c5425fe 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -132,10 +132,7 @@
                         animationFinishCallback.onAnimationFinished(type, anim);
                     }
                 };
-                // If both the Animatable and AnimationAdapter requests to be deferred, only the
-                // first one will be called.
-                if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)
-                        || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
+                if (!anim.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
                     resetAndInvokeFinish.run();
                 }
                 mAnimationFinished = true;
@@ -639,23 +636,5 @@
          * @return The height of the surface to be animated.
          */
         int getSurfaceHeight();
-
-        /**
-         * Gets called when the animation is about to finish and gives the client the opportunity to
-         * defer finishing the animation, i.e. it keeps the leash around until the client calls
-         * {@link #cancelAnimation}.
-         * <p>
-         * {@link AnimationAdapter} has a similar method which is called only if this method returns
-         * false. This mean that if both this {@link Animatable} and the {@link AnimationAdapter}
-         * request to be deferred, this method is the sole responsible to call
-         * endDeferFinishCallback. On the other hand, the animation finish might still be deferred
-         * if this method return false and the one from the {@link AnimationAdapter} returns true.
-         *
-         * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
-         * @return Whether the client would like to defer the animation finish.
-         */
-        default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
-            return false;
-        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f75e717..3abab8b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -508,9 +508,6 @@
      */
     boolean mAllowForceResizeOverride = true;
 
-    private final AnimatingActivityRegistry mAnimatingActivityRegistry =
-            new AnimatingActivityRegistry();
-
     private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_TASK_MSG + 1;
 
     private final Handler mHandler;
@@ -1122,17 +1119,6 @@
         // already ran fully within super.onParentChanged
         updateTaskOrganizerState();
 
-        // TODO(b/168037178): The check for null display content and setting it to null doesn't
-        //                    really make sense here...
-
-        // TODO(b/168037178): This is mostly taking care of the case where the stask is removing
-        //                    from the display, so we should probably consolidate it there instead.
-
-        if (getParent() == null && mDisplayContent != null) {
-            mDisplayContent = null;
-            mWmService.mWindowPlacerLocked.requestTraversal();
-        }
-
         if (oldParent != null) {
             final Task oldParentTask = oldParent.asTask();
             if (oldParentTask != null) {
@@ -1185,9 +1171,6 @@
         }
 
         mRootWindowContainer.updateUIDsPresentOnDisplay();
-
-        // Ensure all animations are finished at same time in split-screen mode.
-        forAllActivities(ActivityRecord::updateAnimatingActivityRegistry);
     }
 
     @Override
@@ -2770,6 +2753,7 @@
         }
 
         super.removeImmediately();
+        mDisplayContent = null;
         mRemoving = false;
     }
 
@@ -3345,13 +3329,6 @@
         mLastSurfaceShowing = show;
     }
 
-    @Override
-    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
-        super.dump(pw, prefix, dumpAll);
-        mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
-    }
-
-
     /**
      * Fills in a {@link TaskInfo} with information from this task. Note that the base intent in the
      * task info will not include any extras or clip data.
@@ -6313,10 +6290,6 @@
         return mDisplayContent.getDisplayInfo();
     }
 
-    AnimatingActivityRegistry getAnimatingActivityRegistry() {
-        return mAnimatingActivityRegistry;
-    }
-
     private Rect getRawBounds() {
         return super.getBounds();
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 324852d..97a1a34 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -394,6 +394,12 @@
      */
     private boolean mAllowTransitionWhenEmpty;
 
+    /**
+     * Specifies which configuration changes should trigger TaskFragment info changed callbacks.
+     * Only system TaskFragment organizers are allowed to set this value.
+     */
+    private @ActivityInfo.Config int mConfigurationChangeMaskForOrganizer;
+
     /** When set, will force the task to report as invisible. */
     static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
     static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -656,6 +662,17 @@
         mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
     }
 
+    void setConfigurationChangeMaskForOrganizer(@ActivityInfo.Config int mask) {
+        // Only system organizers are allowed to set configuration change mask.
+        if (mTaskFragmentOrganizerController.isSystemOrganizer(mTaskFragmentOrganizer.asBinder())) {
+            mConfigurationChangeMaskForOrganizer = mask;
+        }
+    }
+
+    @ActivityInfo.Config int getConfigurationChangeMaskForOrganizer() {
+        return mConfigurationChangeMaskForOrganizer;
+    }
+
     /** @see #mIsolatedNav */
     boolean isIsolatedNav() {
         return isEmbedded() && mIsolatedNav;
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index e63107c..ae329d7 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -349,8 +349,10 @@
             // Check if the info is different from the last reported info.
             final TaskFragmentInfo info = tf.getTaskFragmentInfo();
             final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
-            if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
-                    info.getConfiguration(), lastInfo.getConfiguration())) {
+            final int configurationChangeMask = tf.getConfigurationChangeMaskForOrganizer();
+            if (info.equalsForTaskFragmentOrganizer(lastInfo)
+                    && configurationsAreEqualForOrganizer(info.getConfiguration(),
+                            lastInfo.getConfiguration(), configurationChangeMask)) {
                 return null;
             }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 432ed1d..8a93772 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -113,27 +113,6 @@
                 enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
     }
 
-    // Still needed for legacy transition.(AppTransitionControllerTest)
-    void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
-        if (shouldDisableSnapshots()) {
-            return;
-        }
-        // We need to take a snapshot of the task if and only if all activities of the task are
-        // either closing or hidden.
-        mTmpTasks.clear();
-        for (int i = closingApps.size() - 1; i >= 0; i--) {
-            final ActivityRecord activity = closingApps.valueAt(i);
-            if (activity.isActivityTypeHome()) continue;
-            final Task task = activity.getTask();
-            if (task == null) continue;
-
-            getClosingTasksInner(task, mTmpTasks);
-        }
-        snapshotTasks(mTmpTasks);
-        mTmpTasks.clear();
-        mSkipClosingAppSnapshotTasks.clear();
-    }
-
     /**
      * Adds the given {@param tasks} to the list of tasks which should not have their snapshots
      * taken upon the next processing of the set of closing apps. The caller is responsible for
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 55c2668..7af542f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3359,7 +3359,7 @@
 
     private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
                                     boolean isVoiceInteraction) {
-        if (AppTransitionController.isTaskViewTask(this) || (isOrganized()
+        if ((isOrganized()
                 // TODO(b/161711458): Clean-up when moved to shell.
                 && getWindowingMode() != WINDOWING_MODE_FULLSCREEN
                 && getWindowingMode() != WINDOWING_MODE_FREEFORM
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c54e7c1..d699a68 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,7 +157,6 @@
 import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
 import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
 import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
 
@@ -348,7 +347,6 @@
 import com.android.server.DisplayThread;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerService;
@@ -450,11 +448,6 @@
     /**
      * Use WMShell for app transition.
      */
-    private static final String ENABLE_SHELL_TRANSITIONS = "persist.wm.debug.shell_transit";
-
-    /**
-     * @see #ENABLE_SHELL_TRANSITIONS
-     */
     public static final boolean sEnableShellTransitions = getShellTransitEnabled();
 
     /**
@@ -503,6 +496,8 @@
 
     final StartingSurfaceController mStartingSurfaceController;
 
+    final PresentationController mPresentationController;
+
     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
         @Override
         public void onVrStateChanged(boolean enabled) {
@@ -1433,6 +1428,7 @@
         setGlobalShadowSettings();
         mAnrController = new AnrController(this);
         mStartingSurfaceController = new StartingSurfaceController(this);
+        mPresentationController = new PresentationController();
 
         mBlurController = new BlurController(mContext, mPowerManager);
         mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
@@ -1937,16 +1933,8 @@
             }
             outSizeCompatScale[0] = win.getCompatScaleForClient();
 
-            if (res >= ADD_OKAY
-                    && (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION)) {
-                displayContent.mIsPresenting = true;
-                if (enablePresentationForConnectedDisplays()) {
-                    // A presentation hides all activities behind on the same display.
-                    displayContent.ensureActivitiesVisible(/*starting=*/ null,
-                            /*notifyClients=*/ true);
-                }
-                mDisplayManagerInternal.onPresentation(displayContent.getDisplay().getDisplayId(),
-                        /*isShown=*/ true);
+            if (res >= ADD_OKAY && win.isPresentation()) {
+                mPresentationController.onPresentationAdded(win);
             }
         }
 
@@ -10317,11 +10305,6 @@
     }
 
     private static boolean getShellTransitEnabled() {
-        android.content.pm.FeatureInfo autoFeature = SystemConfig.getInstance()
-                .getAvailableFeatures().get(PackageManager.FEATURE_AUTOMOTIVE);
-        if (autoFeature != null && autoFeature.version >= 0) {
-            return SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true);
-        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 924b9de..3b6a4dc 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2455,10 +2455,28 @@
     /** Whether the configuration changes are important to report back to an organizer. */
     static boolean configurationsAreEqualForOrganizer(
             Configuration newConfig, @Nullable Configuration oldConfig) {
+        return configurationsAreEqualForOrganizer(newConfig, oldConfig, 0 /* additionalMask */);
+    }
+
+    /**
+     * Whether the configuration changes are important to report back to an organizer.
+     *
+     * @param newConfig the new configuration
+     * @param oldConfig the old configuration
+     * @param additionalMask specifies additional configuration changes that the organizer is
+     *                       interested in. If the configuration change matches any bit in the mask,
+     *                       {@code false} is returned.
+     */
+    static boolean configurationsAreEqualForOrganizer(
+            Configuration newConfig, @Nullable Configuration oldConfig,
+            @ActivityInfo.Config int additionalMask) {
         if (oldConfig == null) {
             return false;
         }
         int cfgChanges = newConfig.diff(oldConfig);
+        if ((cfgChanges & additionalMask) != 0) {
+            return false;
+        }
         final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
                 ? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration,
                 true /* compareUndefined */) : 0;
@@ -2665,6 +2683,8 @@
                 ownerActivity.getUid(), ownerActivity.info.processName);
         if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
             taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+            taskFragment.setConfigurationChangeMaskForOrganizer(
+                    creationParams.getConfigurationChangeMask());
         }
         final int position;
         if (creationParams.getPairedPrimaryFragmentToken() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81895c4..5897241 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -182,7 +182,6 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
 import android.annotation.CallSuper;
@@ -2300,15 +2299,8 @@
 
         final int type = mAttrs.type;
 
-        if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
-            // TODO(b/393945496): Make sure that there's one presentation at most per display.
-            dc.mIsPresenting = false;
-            if (enablePresentationForConnectedDisplays()) {
-                // A presentation hides all activities behind on the same display.
-                dc.ensureActivitiesVisible(/*starting=*/ null, /*notifyClients=*/ true);
-            }
-            mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
-                    /*isShown=*/ false);
+        if (isPresentation()) {
+            mWmService.mPresentationController.onPresentationRemoved(this);
         }
         // Check if window provides non decor insets before clearing its provided insets.
         final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
@@ -3337,6 +3329,10 @@
         }
     }
 
+    boolean isPresentation() {
+        return mAttrs.type == TYPE_PRESENTATION || mAttrs.type == TYPE_PRIVATE_PRESENTATION;
+    }
+
     private boolean isOnVirtualDisplay() {
         return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL;
     }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 883cab0..f07e672 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -668,8 +668,10 @@
     }
 
     // TODO(b/383092013): Add topology validation
-    mInputManager->getChoreographer().setDisplayTopology(
-            android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph));
+    const DisplayTopologyGraph displayTopology =
+            android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+    mInputManager->getDispatcher().setDisplayTopology(displayTopology);
+    mInputManager->getChoreographer().setDisplayTopology(displayTopology);
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 2aa0c6b..440eae5 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -446,7 +446,7 @@
         @Override
         public void binderDied() {
             Slog.d(TAG, "Client binder died - clearing session");
-            finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode());
+            finishSession(isUiWaitingForData(), ApiStatus.BINDER_DIED.getMetricCode());
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index ece729f..c21e645 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -16,6 +16,7 @@
 
 package com.android.server.credentials.metrics;
 
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_BINDER_DIED;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_FAILURE;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_SUCCESS;
@@ -27,7 +28,9 @@
     CLIENT_CANCELED(
             CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED),
     USER_CANCELED(
-            CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED);
+            CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED),
+    BINDER_DIED(
+            CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_BINDER_DIED);
 
     private final int mInnerMetricCode;
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e11c31c..191c21e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8288,8 +8288,11 @@
         Preconditions.checkCallAuthorization(isSystemUid(caller));
         // Managed Profile password can only be changed when it has a separate challenge.
         if (!isSeparateProfileChallengeEnabled(userId)) {
-            Preconditions.checkCallAuthorization(!isManagedProfile(userId), "You can "
-                    + "not set the active password for a managed profile, userId = %d", userId);
+            if (isManagedProfile(userId)) {
+                Slogf.i(LOG_TAG, "You can not set the active password for a managed profile,"
+                        + " userId = %d", userId);
+                return;
+            }
         }
 
         DevicePolicyData policy = getUserData(userId);
@@ -15993,8 +15996,6 @@
         @Override
         public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason,
                 boolean isSafe) {
-            // TODO(b/178494483): use EventLog instead
-            // TODO(b/178494483): log metrics?
             if (VERBOSE_LOG) {
                 Slogf.v(LOG_TAG, "notifyUnsafeOperationStateChanged(): %s=%b",
                         DevicePolicyManager.operationSafetyReasonToString(reason), isSafe);
@@ -16006,16 +16007,20 @@
             extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
             extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
 
-            if (mOwners.hasDeviceOwner()) {
-                if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying DO");
-                sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
-                        extras);
-            }
-            for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
-                if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying PO for user " + profileOwnerId);
-                sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
-                        extras, profileOwnerId);
-            }
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                if (mOwners.hasDeviceOwner()) {
+                    if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying DO");
+                    sendDeviceOwnerCommand(
+                            DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+                            extras);
+                }
+                for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
+                    if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Notifying PO for user " + profileOwnerId);
+                    sendProfileOwnerCommand(
+                            DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+                            extras, profileOwnerId);
+                }
+            });
         }
 
         private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 6e038f9..ba02122 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -54,17 +54,7 @@
         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
         Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
 
-        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;
+        mImei = telephonyService.getImei(0);
         String meid;
         try {
             meid = telephonyService.getMeid(0);
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index dae481a..36947a2 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -3198,8 +3198,10 @@
     dprintf(fd, "    }\n");
 }
 
-void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) {
+binder::Status IncrementalService::AppOpsListener::opChanged(int32_t, int32_t,
+                                                             const String16&, const String16&) {
     incrementalService.onAppOpChanged(packageName);
+    return binder::Status::ok();
 }
 
 binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams(
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index b81e1b1..4ee1a70 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -26,7 +26,7 @@
 #include <android/os/incremental/BnStorageLoadingProgressListener.h>
 #include <android/os/incremental/PerUidReadTimeouts.h>
 #include <android/os/incremental/StorageHealthCheckParams.h>
-#include <binder/IAppOpsCallback.h>
+#include <binder/AppOpsManager.h>
 #include <binder/PersistableBundle.h>
 #include <utils/String16.h>
 #include <utils/StrongPointer.h>
@@ -200,11 +200,12 @@
 
     void getMetrics(int32_t storageId, android::os::PersistableBundle* _aidl_return);
 
-    class AppOpsListener : public android::BnAppOpsCallback {
+    class AppOpsListener : public com::android::internal::app::BnAppOpsCallback {
     public:
         AppOpsListener(IncrementalService& incrementalService, std::string packageName)
               : incrementalService(incrementalService), packageName(std::move(packageName)) {}
-        void opChanged(int32_t op, const String16& packageName) final;
+        binder::Status opChanged(int32_t op, int32_t uid, const String16& packageName,
+                                 const String16& persistentDeviceId) final;
 
     private:
         IncrementalService& incrementalService;
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 39e2ee3..36a5b7f 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -23,7 +23,7 @@
 #include <android/content/pm/IDataLoader.h>
 #include <android/content/pm/IDataLoaderStatusListener.h>
 #include <android/os/incremental/PerUidReadTimeouts.h>
-#include <binder/IAppOpsCallback.h>
+#include <binder/AppOpsManager.h>
 #include <binder/IServiceManager.h>
 #include <binder/Status.h>
 #include <incfs.h>
@@ -133,6 +133,7 @@
 
 class AppOpsManagerWrapper {
 public:
+    using IAppOpsCallback = ::com::android::internal::app::IAppOpsCallback;
     virtual ~AppOpsManagerWrapper() = default;
     virtual binder::Status checkPermission(const char* permission, const char* operation,
                                            const char* package) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index d9d3d62..73849a3 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -1678,7 +1678,7 @@
                                                   {}, {}));
     ASSERT_GE(mDataLoader->setStorageParams(true), 0);
     ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
-    mAppOpsManager->mStoredCallback->opChanged(0, {});
+    mAppOpsManager->mStoredCallback->opChanged(0, 0, {}, {});
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c974d9e..2bbd69c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -894,6 +894,17 @@
 
             SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
 
+            // Prepare the thread pool for init tasks that can be parallelized
+            SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
+            mDumper.addDumpable(tp);
+
+            if (android.server.Flags.earlySystemConfigInit()) {
+                // SystemConfig init is expensive, so enqueue the work as early as possible to allow
+                // concurrent execution before it's needed (typically by ActivityManagerService).
+                // As native library loading is also expensive, this is a good place to start.
+                startSystemConfigInit(t);
+            }
+
             // Initialize native services.
             System.loadLibrary("android_servers");
 
@@ -926,9 +937,6 @@
             mDumper.addDumpable(mSystemServiceManager);
 
             LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
-            // Prepare the thread pool for init tasks that can be parallelized
-            SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
-            mDumper.addDumpable(tp);
 
             // Lazily load the pre-installed system font map in SystemServer only if we're not doing
             // the optimized font loading in the FontManagerService.
@@ -1093,6 +1101,14 @@
         }
     }
 
+    private void startSystemConfigInit(TimingsTraceAndSlog t) {
+        Slog.i(TAG, "Reading configuration...");
+        final String tagSystemConfig = "ReadingSystemConfig";
+        t.traceBegin(tagSystemConfig);
+        SystemServerInitThreadPool.submit(SystemConfig::getInstance, tagSystemConfig);
+        t.traceEnd();
+    }
+
     private void createSystemContext() {
         ActivityThread activityThread = ActivityThread.systemMain();
         mSystemContext = activityThread.getSystemContext();
@@ -1131,11 +1147,11 @@
         mDumper.addDumpable(watchdog);
         t.traceEnd();
 
-        Slog.i(TAG, "Reading configuration...");
-        final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
-        t.traceBegin(TAG_SYSTEM_CONFIG);
-        SystemServerInitThreadPool.submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
-        t.traceEnd();
+        // Legacy entry point for starting SystemConfig init, only needed if the early init flag is
+        // disabled and we haven't already triggered init before bootstrap services.
+        if (!android.server.Flags.earlySystemConfigInit()) {
+            startSystemConfigInit(t);
+        }
 
         // Orchestrates some ProtoLogging functionality.
         if (android.tracing.Flags.clientSideProtoLogging()) {
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 4d021ec..86ccd87 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -10,6 +10,13 @@
 }
 
 flag {
+     namespace: "system_performance"
+     name: "early_system_config_init"
+     description: "Perform earlier initialization of SystemConfig in system server startup."
+     bug: "383869534"
+}
+
+flag {
      name: "remove_text_service"
      namespace: "wear_frameworks"
      description: "Remove TextServiceManagerService on Wear"
diff --git a/services/proguard.flags b/services/proguard.flags
index 0e1f68e..8d8b418 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -15,7 +15,10 @@
 
 # APIs referenced by dependent JAR files and modules
 # TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro.
--keep interface android.annotation.SystemApi
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep interface android.annotation.SystemApi {
+  void <init>();
+}
 -keep @android.annotation.SystemApi class * {
   public protected *;
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index a103b05..72e9cc5 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -18,6 +18,10 @@
 
 import static android.view.WindowInsets.Type.captionBar;
 
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_CONFIG;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_HIDE;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_SHOW;
+import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.eventToString;
 import static com.android.compatibility.common.util.SystemUtil.eventually;
 import static com.android.cts.input.injectinputinprocess.InjectInputInProcessKt.clickOnViewCenter;
 import static com.android.internal.inputmethod.InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR;
@@ -31,7 +35,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
-import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.os.RemoteException;
@@ -42,7 +45,6 @@
 import android.server.wm.WindowManagerStateHelper;
 import android.util.Log;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicyConstants;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
@@ -58,7 +60,9 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.Event;
 import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+import com.android.compatibility.common.util.GestureNavSwitchHelper;
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
@@ -90,6 +94,8 @@
 
     private final WindowManagerStateHelper mWmState =  new WindowManagerStateHelper();
 
+    private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();
+
     private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
 
     @Rule
@@ -100,7 +106,6 @@
 
     private Instrumentation mInstrumentation;
     private UiDevice mUiDevice;
-    private Context mContext;
     private InputMethodManager mImm;
     private String mTargetPackageName;
     private String mInputMethodId;
@@ -112,8 +117,7 @@
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mUiDevice = UiDevice.getInstance(mInstrumentation);
-        mContext = mInstrumentation.getContext();
-        mImm = mContext.getSystemService(InputMethodManager.class);
+        mImm = mInstrumentation.getContext().getSystemService(InputMethodManager.class);
         mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
         mInputMethodId = getInputMethodId();
         prepareIme();
@@ -169,6 +173,7 @@
         Log.i(TAG, "Click on EditText");
         verifyInputViewStatus(
                 () -> clickOnViewCenter(mActivity.getEditText()),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -185,6 +190,7 @@
             verifyInputViewStatus(
                     () -> assertWithMessage("Home key press was handled")
                             .that(mUiDevice.pressHome()).isTrue(),
+                    EVENT_HIDE,
                     true /* expected */,
                     false /* inputViewStarted */);
             assertWithMessage("IME is not shown")
@@ -202,6 +208,7 @@
         // Triggers to show IME via public API.
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -209,6 +216,7 @@
         // Triggers to hide IME via public API.
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                EVENT_HIDE,
                 true /* expected */,
                 false /* inputViewStarted */);
         if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
@@ -232,6 +240,7 @@
         // Triggers to show IME via public API.
         verifyInputViewStatusOnMainSync(
                 () -> mActivity.showImeWithWindowInsetsController(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -239,6 +248,7 @@
         // Triggers to hide IME via public API.
         verifyInputViewStatusOnMainSync(
                 () -> mActivity.hideImeWithWindowInsetsController(),
+                EVENT_HIDE,
                 true /* expected */,
                 false /* inputViewStarted */);
         if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
@@ -267,6 +277,7 @@
         Log.i(TAG, "Call IMS#requestShowSelf(0)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestShowSelf(0 /* flags */),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -277,6 +288,7 @@
             verifyInputViewStatusOnMainSync(
                     () -> mInputMethodService.requestHideSelf(
                             InputMethodManager.HIDE_IMPLICIT_ONLY),
+                    EVENT_HIDE,
                     false /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is still shown after HIDE_IMPLICIT_ONLY")
@@ -287,6 +299,7 @@
         Log.i(TAG, "Call IMS#requestHideSelf(0)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(0 /* flags */),
+                EVENT_HIDE,
                 true /* expected */,
                 false /* inputViewStarted */);
         if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
@@ -304,6 +317,7 @@
             Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
             verifyInputViewStatusOnMainSync(
                     () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+                    EVENT_SHOW,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is shown with SHOW_IMPLICIT")
@@ -314,6 +328,7 @@
             verifyInputViewStatusOnMainSync(
                     () -> mInputMethodService.requestHideSelf(
                             InputMethodManager.HIDE_IMPLICIT_ONLY),
+                    EVENT_HIDE,
                     true /* expected */,
                     false /* inputViewStarted */);
             assertWithMessage("IME is not shown after HIDE_IMPLICIT_ONLY")
@@ -409,6 +424,7 @@
             verifyInputViewStatusOnMainSync(() -> assertThat(
                             mActivity.showImeWithInputMethodManager(
                                     InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                    EVENT_SHOW,
                     false /* expected */,
                     false /* inputViewStarted */);
             assertWithMessage("IME is not shown after SHOW_IMPLICIT")
@@ -417,6 +433,7 @@
             verifyInputViewStatusOnMainSync(
                     () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
                             .isTrue(),
+                    EVENT_SHOW,
                     false /* expected */,
                     false /* inputViewStarted */);
             assertWithMessage("IME is not shown after SHOW_EXPLICIT")
@@ -438,6 +455,7 @@
         // IME should be shown.
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -454,6 +472,7 @@
         // the IME should be shown.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -483,6 +502,7 @@
 
         verifyInputViewStatusOnMainSync(() -> assertThat(
                         mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -517,6 +537,7 @@
 
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                EVENT_SHOW,
                 false /* expected */,
                 false /* inputViewStarted */);
         assertWithMessage("IME is not shown")
@@ -540,6 +561,7 @@
 
             verifyInputViewStatusOnMainSync(() -> assertThat(
                             mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                    EVENT_SHOW,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -572,6 +594,7 @@
             verifyInputViewStatusOnMainSync(() ->assertThat(
                     mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT))
                             .isTrue(),
+                    EVENT_SHOW,
                     false /* expected */,
                     false /* inputViewStarted */);
             assertWithMessage("IME is not shown")
@@ -600,6 +623,7 @@
             verifyInputViewStatusOnMainSync(
                     () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */))
                             .isTrue(),
+                    EVENT_SHOW,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -613,6 +637,7 @@
 
             verifyInputViewStatusOnMainSync(
                     () -> mInputMethodService.onConfigurationChanged(config),
+                    EVENT_CONFIG,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is still shown after a configuration change")
@@ -647,6 +672,7 @@
             verifyInputViewStatusOnMainSync(() -> assertThat(
                             mActivity.showImeWithInputMethodManager(
                                     InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                    EVENT_SHOW,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -666,6 +692,7 @@
             // still alive.
             verifyInputViewStatusOnMainSync(
                     () -> mInputMethodService.onConfigurationChanged(config),
+                    EVENT_CONFIG,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is not shown after a configuration change")
@@ -695,6 +722,7 @@
             // Explicit show request.
             verifyInputViewStatusOnMainSync(() -> assertThat(
                             mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                    EVENT_SHOW,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -703,6 +731,7 @@
             verifyInputViewStatusOnMainSync(() -> assertThat(
                             mActivity.showImeWithInputMethodManager(
                                     InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                    EVENT_SHOW,
                     false /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is still shown")
@@ -713,6 +742,7 @@
             // explicit show request, and thus not hide the IME.
             verifyInputViewStatusOnMainSync(
                     () -> mInputMethodService.onConfigurationChanged(config),
+                    EVENT_CONFIG,
                     true /* expected */,
                     true /* inputViewStarted */);
             assertWithMessage("IME is still shown after a configuration change")
@@ -739,12 +769,14 @@
 
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
 
         verifyInputViewStatusOnMainSync(() -> assertThat(
                         mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                EVENT_SHOW,
                 false /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is still shown")
@@ -753,6 +785,7 @@
         verifyInputViewStatusOnMainSync(() -> assertThat(
                         mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS))
                         .isTrue(),
+                EVENT_HIDE,
                 false /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is still shown after HIDE_NOT_ALWAYS")
@@ -812,6 +845,7 @@
                     setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
                     mActivity.showImeWithWindowInsetsController();
                 },
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -849,6 +883,7 @@
                     setDrawsImeNavBarAndSwitcherButton(false /* enabled */);
                     mActivity.showImeWithWindowInsetsController();
                 },
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -872,35 +907,34 @@
      * Verifies that clicking on the IME navigation bar back button hides the IME.
      */
     @Test
-    public void testBackButtonClick() {
+    public void testBackButtonClick() throws Exception {
         assumeTrue("Must have a navigation bar", hasNavigationBar());
-        assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
 
         waitUntilActivityReadyForInputInjection(mActivity);
 
         setShowImeWithHardKeyboard(true /* enabled */);
 
-        verifyInputViewStatusOnMainSync(
-                () -> {
-                    setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
-                    mActivity.showImeWithWindowInsetsController();
-                },
-                true /* expected */,
-                true /* inputViewStarted */);
-        assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+        try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+            verifyInputViewStatusOnMainSync(
+                    () -> mActivity.showImeWithWindowInsetsController(),
+                    EVENT_SHOW,
+                    true /* expected */,
+                    true /* inputViewStarted */);
+            assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
 
-        final var backButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
-        backButtonUiObject.click();
-        mInstrumentation.waitForIdleSync();
+            final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
+            backButton.click();
+            mInstrumentation.waitForIdleSync();
 
-        if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
-            // The IME visibility is only sent at the end of the animation. Therefore, we have to
-            // wait until the visibility was sent to the server and the IME window hidden.
-            eventually(() -> assertWithMessage("IME is not shown")
-                    .that(mInputMethodService.isInputViewShown()).isFalse());
-        } else {
-            assertWithMessage("IME is not shown")
-                    .that(mInputMethodService.isInputViewShown()).isFalse();
+            if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+                // The IME visibility is only sent at the end of the animation. Therefore, we have
+                // to wait until the visibility was sent to the server and the IME window hidden.
+                eventually(() -> assertWithMessage("IME is not shown")
+                        .that(mInputMethodService.isInputViewShown()).isFalse());
+            } else {
+                assertWithMessage("IME is not shown")
+                        .that(mInputMethodService.isInputViewShown()).isFalse();
+            }
         }
     }
 
@@ -908,35 +942,34 @@
      * Verifies that long clicking on the IME navigation bar back button hides the IME.
      */
     @Test
-    public void testBackButtonLongClick() {
+    public void testBackButtonLongClick() throws Exception {
         assumeTrue("Must have a navigation bar", hasNavigationBar());
-        assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
 
         waitUntilActivityReadyForInputInjection(mActivity);
 
         setShowImeWithHardKeyboard(true /* enabled */);
 
-        verifyInputViewStatusOnMainSync(
-                () -> {
-                    setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
-                    mActivity.showImeWithWindowInsetsController();
-                },
-                true /* expected */,
-                true /* inputViewStarted */);
-        assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+        try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+            verifyInputViewStatusOnMainSync(
+                    () -> mActivity.showImeWithWindowInsetsController(),
+                    EVENT_SHOW,
+                    true /* expected */,
+                    true /* inputViewStarted */);
+            assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
 
-        final var backButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
-        backButtonUiObject.longClick();
-        mInstrumentation.waitForIdleSync();
+            final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
+            backButton.longClick();
+            mInstrumentation.waitForIdleSync();
 
-        if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
-            // The IME visibility is only sent at the end of the animation. Therefore, we have to
-            // wait until the visibility was sent to the server and the IME window hidden.
-            eventually(() -> assertWithMessage("IME is not shown")
-                    .that(mInputMethodService.isInputViewShown()).isFalse());
-        } else {
-            assertWithMessage("IME is not shown")
-                    .that(mInputMethodService.isInputViewShown()).isFalse();
+            if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+                // The IME visibility is only sent at the end of the animation. Therefore, we have
+                // to wait until the visibility was sent to the server and the IME window hidden.
+                eventually(() -> assertWithMessage("IME is not shown")
+                        .that(mInputMethodService.isInputViewShown()).isFalse());
+            } else {
+                assertWithMessage("IME is not shown")
+                        .that(mInputMethodService.isInputViewShown()).isFalse();
+            }
         }
     }
 
@@ -945,103 +978,108 @@
      * or switches the input method.
      */
     @Test
-    public void testImeSwitchButtonClick() {
+    public void testImeSwitchButtonClick() throws Exception {
         assumeTrue("Must have a navigation bar", hasNavigationBar());
-        assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
 
         waitUntilActivityReadyForInputInjection(mActivity);
 
         setShowImeWithHardKeyboard(true /* enabled */);
 
-        verifyInputViewStatusOnMainSync(
-                () -> {
-                    setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
-                    mActivity.showImeWithWindowInsetsController();
-                },
-                true /* expected */,
-                true /* inputViewStarted */);
-        assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+        try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+            verifyInputViewStatusOnMainSync(
+                    () -> {
+                        setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+                        mActivity.showImeWithWindowInsetsController();
+                    },
+                    EVENT_SHOW,
+                    true /* expected */,
+                    true /* inputViewStarted */);
+            assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
 
-        final var initialInfo = mImm.getCurrentInputMethodInfo();
+            final var initialInfo = mImm.getCurrentInputMethodInfo();
 
-        final var imeSwitchButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
-        imeSwitchButtonUiObject.click();
-        mInstrumentation.waitForIdleSync();
+            final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
+            imeSwitcherButton.click();
+            mInstrumentation.waitForIdleSync();
 
-        final var newInfo = mImm.getCurrentInputMethodInfo();
+            final var newInfo = mImm.getCurrentInputMethodInfo();
 
-        assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
-                .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
-                .isTrue();
+            assertWithMessage("Input Method Switcher Menu is shown or input method was switched")
+                    .that(isInputMethodPickerShown(mImm) || !Objects.equals(initialInfo, newInfo))
+                    .isTrue();
 
-        assertWithMessage("IME is still shown after IME Switcher button was clicked")
-                .that(mInputMethodService.isInputViewShown()).isTrue();
+            assertWithMessage("IME is still shown after IME Switcher button was clicked")
+                    .that(mInputMethodService.isInputViewShown()).isTrue();
 
-        // Hide the IME Switcher Menu before finishing.
-        mUiDevice.pressBack();
+            // Hide the IME Switcher Menu before finishing.
+            mUiDevice.pressBack();
+        }
     }
 
     /**
      * Verifies that long clicking on the IME switch button shows the Input Method Switcher Menu.
      */
     @Test
-    public void testImeSwitchButtonLongClick() {
+    public void testImeSwitchButtonLongClick() throws Exception {
         assumeTrue("Must have a navigation bar", hasNavigationBar());
-        assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
 
         waitUntilActivityReadyForInputInjection(mActivity);
 
         setShowImeWithHardKeyboard(true /* enabled */);
 
-        verifyInputViewStatusOnMainSync(
-                () -> {
-                    setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
-                    mActivity.showImeWithWindowInsetsController();
-                },
-                true /* expected */,
-                true /* inputViewStarted */);
-        assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+        try (var ignored = mGestureNavSwitchHelper.withGestureNavigationMode()) {
+            verifyInputViewStatusOnMainSync(
+                    () -> {
+                        setDrawsImeNavBarAndSwitcherButton(true /* enabled */);
+                        mActivity.showImeWithWindowInsetsController();
+                    },
+                    EVENT_SHOW,
+                    true /* expected */,
+                    true /* inputViewStarted */);
+            assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
 
-        final var imeSwitchButtonUiObject = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
-        imeSwitchButtonUiObject.longClick();
-        mInstrumentation.waitForIdleSync();
+            final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
+            imeSwitcherButton.longClick();
+            mInstrumentation.waitForIdleSync();
 
-        assertWithMessage("Input Method Switcher Menu is shown")
-                .that(isInputMethodPickerShown(mImm)).isTrue();
-        assertWithMessage("IME is still shown after IME Switcher button was long clicked")
-                .that(mInputMethodService.isInputViewShown()).isTrue();
+            assertWithMessage("Input Method Switcher Menu is shown")
+                    .that(isInputMethodPickerShown(mImm)).isTrue();
+            assertWithMessage("IME is still shown after IME Switcher button was long clicked")
+                    .that(mInputMethodService.isInputViewShown()).isTrue();
 
-        // Hide the IME Switcher Menu before finishing.
-        mUiDevice.pressBack();
+            // Hide the IME Switcher Menu before finishing.
+            mUiDevice.pressBack();
+        }
     }
 
-    private void verifyInputViewStatus(@NonNull Runnable runnable, boolean expected,
-            boolean inputViewStarted) {
-        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+    private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int event,
+            boolean expected, boolean inputViewStarted) {
+        verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
                 false /* runOnMainSync */);
     }
 
-    private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, boolean expected,
-            boolean inputViewStarted) {
-        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+    private void verifyInputViewStatusOnMainSync(@NonNull Runnable runnable, @Event int event,
+            boolean expected, boolean inputViewStarted) {
+        verifyInputViewStatusInternal(runnable, event, expected, inputViewStarted,
                 true /* runOnMainSync */);
     }
 
     /**
-     * Verifies the status of the Input View after executing the given runnable.
+     * Verifies the status of the Input View after executing the given runnable, and waiting that
+     * the event was either triggered or not, based on the given expectation.
      *
-     * @param runnable         the runnable to execute for showing or hiding the IME.
-     * @param expected         whether the runnable is expected to trigger the signal.
+     * @param runnable         the runnable to trigger the event
+     * @param event            the event to await.
+     * @param expected         whether the event is expected to be triggered.
      * @param inputViewStarted the expected state of the Input View after executing the runnable.
      * @param runOnMainSync    whether to execute the runnable on the main thread.
      */
-    private void verifyInputViewStatusInternal(@NonNull Runnable runnable, boolean expected,
-            boolean inputViewStarted, boolean runOnMainSync) {
+    private void verifyInputViewStatusInternal(@NonNull Runnable runnable, @Event int event,
+            boolean expected, boolean inputViewStarted, boolean runOnMainSync) {
         final boolean completed;
         try {
             final var latch = new CountDownLatch(1);
-            mInputMethodService.setCountDownLatchForTesting(latch);
-            // Trigger onStartInputView() / onFinishInputView() / onConfigurationChanged()
+            mInputMethodService.setCountDownLatchForTesting(latch, event);
             if (runOnMainSync) {
                 mInstrumentation.runOnMainSync(runnable);
             } else {
@@ -1053,15 +1091,13 @@
             fail("Interrupted while waiting for latch: " + e.getMessage());
             return;
         } finally {
-            mInputMethodService.setCountDownLatchForTesting(null);
+            mInputMethodService.setCountDownLatchForTesting(null /* latch */, event);
         }
 
         if (expected && !completed) {
-            fail("Timed out waiting for"
-                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+            fail("Timed out waiting for " + eventToString(event));
         } else if (!expected && completed) {
-            fail("Unexpected call"
-                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+            fail("Unexpected call " + eventToString(event));
         }
         // Input is not finished.
         assertWithMessage("Input connection is still started")
@@ -1097,7 +1133,7 @@
      */
     private void verifyFullscreenMode(@NonNull Runnable runnable, boolean expected,
             boolean orientationPortrait) {
-        verifyInputViewStatus(runnable, expected, false /* inputViewStarted */);
+        verifyInputViewStatus(runnable, EVENT_CONFIG, expected, false /* inputViewStarted */);
         if (expected) {
             // Wait for the TestActivity to be recreated.
             eventually(() -> assertWithMessage("Activity was re-created after rotation")
@@ -1105,10 +1141,14 @@
             // Get the new TestActivity.
             mActivity = TestActivity.getLastCreatedInstance();
             assertWithMessage("Re-created activity is not null").that(mActivity).isNotNull();
+            // Wait for the new EditText to be served by InputMethodManager.
+            eventually(() -> assertWithMessage("Has an input connection to the re-created Activity")
+                    .that(mImm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
         }
 
         verifyInputViewStatusOnMainSync(
                 () -> mActivity.showImeWithWindowInsetsController(),
+                EVENT_SHOW,
                 true /* expected */,
                 true /* inputViewStarted */);
         assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
@@ -1134,6 +1174,7 @@
         // Hide IME before finishing the run.
         verifyInputViewStatusOnMainSync(
                 () -> mActivity.hideImeWithWindowInsetsController(),
+                EVENT_HIDE,
                 true /* expected */,
                 false /* inputViewStarted */);
 
@@ -1214,18 +1255,12 @@
         return uiObject;
     }
 
-    /** Checks whether gesture navigation move is enabled. */
-    private boolean isGestureNavEnabled() {
-        return mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_navBarInteractionMode)
-                == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-    }
-
     /** Checks whether the device has a navigation bar on the IME's display. */
     private boolean hasNavigationBar() {
         try {
             return WindowManagerGlobal.getWindowManagerService()
-                    .hasNavigationBar(mInputMethodService.getDisplayId());
+                    .hasNavigationBar(mInputMethodService.getDisplayId())
+                    && mGestureNavSwitchHelper.hasNavigationBar();
         } catch (RemoteException e) {
             fail("Failed to check whether the device has a navigation bar: " + e.getMessage());
             return false;
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index 8a12dcd..e2362f7 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -53,6 +53,9 @@
     srcs: [
         "src/com/android/apps/inputmethod/simpleime/ims/*.java",
     ],
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
     sdk_version: "current",
 }
 
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index cf7d660..00873de 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -42,6 +42,7 @@
         <activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
                   android:exported="false"
                   android:label="TestActivity"
+                  android:configChanges="assetsPaths"
                   android:launchMode="singleInstance"
                   android:excludeFromRecents="true"
                   android:noHistory="true"
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
index be59dd2..3a7abbb 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -21,6 +21,12 @@
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.CountDownLatch;
 
 /** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
@@ -29,16 +35,41 @@
     private static final String TAG = "InputMethodServiceWrapper";
 
     /** Last created instance of this wrapper. */
+    @Nullable
     private static InputMethodServiceWrapper sInstance;
 
+    /** IME show event ({@link #onStartInputView}). */
+    public static final int EVENT_SHOW = 0;
+
+    /** IME hide event ({@link #onFinishInputView}). */
+    public static final int EVENT_HIDE = 1;
+
+    /** IME configuration change event ({@link #onConfigurationChanged}). */
+    public static final int EVENT_CONFIG = 2;
+
+    /** The type of event that can be waited with a latch. */
+    @IntDef(value = {
+            EVENT_SHOW,
+            EVENT_HIDE,
+            EVENT_CONFIG,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Event {}
+
+    /** The IME event type that the current latch, if any, waits on. */
+    @Event
+    private int mLatchEvent;
+
     private boolean mInputViewStarted;
 
     /**
      * @see #setCountDownLatchForTesting
      */
-    private CountDownLatch mCountDownLatchForTesting;
+    @Nullable
+    private CountDownLatch mCountDownLatch;
 
     /** Gets the last created instance of this wrapper, if available. */
+    @Nullable
     public static InputMethodServiceWrapper getInstance() {
         return sInstance;
     }
@@ -48,14 +79,14 @@
     }
 
     /**
-     * Sets the latch used to wait for the IME to start showing ({@link #onStartInputView},
-     * start hiding ({@link #onFinishInputView}) or receive a configuration change
-     * ({@link #onConfigurationChanged}).
+     * Sets the latch used to wait for the IME event.
      *
-     * @param countDownLatchForTesting the latch to wait on.
+     * @param latch      the latch to wait on.
+     * @param latchEvent the event to set the latch on.
      */
-    public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
-        mCountDownLatchForTesting = countDownLatchForTesting;
+    public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) {
+        mCountDownLatch = latch;
+        mLatchEvent = latchEvent;
     }
 
     @Override
@@ -77,8 +108,8 @@
                 + ", restarting=" + restarting);
         super.onStartInputView(info, restarting);
         mInputViewStarted = true;
-        if (mCountDownLatchForTesting != null) {
-            mCountDownLatchForTesting.countDown();
+        if (mCountDownLatch != null && mLatchEvent == EVENT_SHOW) {
+            mCountDownLatch.countDown();
         }
     }
 
@@ -94,8 +125,8 @@
         super.onFinishInputView(finishingInput);
         mInputViewStarted = false;
 
-        if (mCountDownLatchForTesting != null) {
-            mCountDownLatchForTesting.countDown();
+        if (mCountDownLatch != null && mLatchEvent == EVENT_HIDE) {
+            mCountDownLatch.countDown();
         }
     }
 
@@ -110,11 +141,27 @@
         Log.i(TAG, "onConfigurationChanged() " + newConfig);
         super.onConfigurationChanged(newConfig);
 
-        if (mCountDownLatchForTesting != null) {
-            mCountDownLatchForTesting.countDown();
+        if (mCountDownLatch != null && mLatchEvent == EVENT_CONFIG) {
+            mCountDownLatch.countDown();
         }
     }
 
+    /**
+     * Gets the string representation of the IME event that is being waited on.
+     *
+     * @param event the IME event.
+     */
+    @NonNull
+    public static String eventToString(@Event int event) {
+        return switch (event) {
+            case EVENT_SHOW -> "onStartInputView";
+            case EVENT_HIDE -> "onFinishInputView";
+            case EVENT_CONFIG -> "onConfigurationChanged";
+            default -> "unknownEvent";
+        };
+    }
+
+    @NonNull
     private String dumpEditorInfo(EditorInfo info) {
         if (info == null) {
             return "null";
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index f154dbc..09ce263 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3962,7 +3962,7 @@
         }
 
         @Override
-        public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+        public VotesStatsReporter getVotesStatsReporter() {
             return null;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
index 1e665c2..409706b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
@@ -1550,6 +1550,118 @@
         verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2));
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testDeliveryGroupPolicy_sameAction_multiplePolicies() {
+        // Create a PACKAGE_CHANGED broadcast corresponding to a change in the whole PACKAGE_GREEN
+        // package.
+        final Intent greenPackageChangedIntent = createPackageChangedIntent(
+                getUidForPackage(PACKAGE_GREEN), List.of(PACKAGE_GREEN));
+        // Create delivery group policy such that when there are multiple broadcasts within the
+        // delivery group identified by "com.example.green/10002", only the most recent one
+        // gets delivered and the rest get discarded.
+        final BroadcastOptions optionsMostRecentPolicyForPackageGreen =
+                BroadcastOptions.makeBasic();
+        optionsMostRecentPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+                PACKAGE_GREEN + "/" + getUidForPackage(PACKAGE_GREEN));
+        optionsMostRecentPolicyForPackageGreen.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+        // Create a PACKAGE_CHANGED broadcast corresponding to a change in the whole PACKAGE_RED
+        // package.
+        final Intent redPackageChangedIntent = createPackageChangedIntent(
+                getUidForPackage(PACKAGE_RED), List.of(PACKAGE_RED));
+        // Create delivery group policy such that when there are multiple broadcasts within the
+        // delivery group identified by "com.example.red/10001", only the most recent one
+        // gets delivered and the rest get discarded.
+        final BroadcastOptions optionsMostRecentPolicyForPackageRed =
+                BroadcastOptions.makeBasic();
+        optionsMostRecentPolicyForPackageRed.setDeliveryGroupMatchingKey("package_changed",
+                PACKAGE_RED + "/" + getUidForPackage(PACKAGE_RED));
+        optionsMostRecentPolicyForPackageRed.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+        // Create a PACKAGE_CHANGED broadcast corresponding to a change in some components of
+        // PACKAGE_GREEN package.
+        final Intent greenPackageComponentsChangedIntent1 = createPackageChangedIntent(
+                getUidForPackage(PACKAGE_GREEN),
+                List.of(PACKAGE_GREEN + ".comp1", PACKAGE_GREEN + ".comp2"));
+        final Intent greenPackageComponentsChangedIntent2 = createPackageChangedIntent(
+                getUidForPackage(PACKAGE_GREEN),
+                List.of(PACKAGE_GREEN + ".comp3"));
+        // Create delivery group policy such that when there are multiple broadcasts within the
+        // delivery group identified by "components-com.example.green/10002", merge the extras
+        // within these broadcasts such that only one broadcast is sent and the rest are
+        // discarded. Couple of things to note here:
+        // 1. We are intentionally using a different policy group
+        //    "components-com.example.green/10002" (as opposed to "com.example.green/10002" used
+        //    earlier), because this is corresponding to a change in some particular components,
+        //    rather than a change to the whole package and we want to keep these two types of
+        //    broadcasts independent.
+        // 2. We are using 'extrasMerger' to indicate how we want the extras to be merged. This
+        //    assumes that broadcasts belonging to the group 'components-com.example.green/10002'
+        //    will have the same values for all the extras, except for the one extra
+        //    'EXTRA_CHANGED_COMPONENT_NAME_LIST'. So, we explicitly specify how to merge this
+        //    extra by using 'STRATEGY_ARRAY_APPEND' strategy, which basically indicates that
+        //    the extra values which are arrays should be concatenated.
+        final BundleMerger extrasMerger = new BundleMerger();
+        extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                BundleMerger.STRATEGY_ARRAY_APPEND);
+        final BroadcastOptions optionsMergedPolicyForPackageGreen = BroadcastOptions.makeBasic();
+        optionsMergedPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+                "components-" + PACKAGE_GREEN + "/" + getUidForPackage(PACKAGE_GREEN));
+        optionsMergedPolicyForPackageGreen.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+        optionsMergedPolicyForPackageGreen.setDeliveryGroupExtrasMerger(extrasMerger);
+
+        // Create a PACKAGE_CHANGED broadcast corresponding to a change in some components of
+        // PACKAGE_RED package.
+        final Intent redPackageComponentsChangedIntent = createPackageChangedIntent(
+                getUidForPackage(PACKAGE_RED),
+                List.of(PACKAGE_RED + ".comp1", PACKAGE_RED + ".comp2"));
+        // Create delivery group policy such that when there are multiple broadcasts within the
+        // delivery group identified by "components-com.example.red/10001", merge the extras
+        // within these broadcasts such that only one broadcast is sent and the rest are
+        // discarded.
+        final BroadcastOptions optionsMergedPolicyForPackageRed = BroadcastOptions.makeBasic();
+        optionsMergedPolicyForPackageGreen.setDeliveryGroupMatchingKey("package_changed",
+                "components-" + PACKAGE_RED + "/" + getUidForPackage(PACKAGE_RED));
+        optionsMergedPolicyForPackageRed.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+        optionsMergedPolicyForPackageRed.setDeliveryGroupExtrasMerger(extrasMerger);
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageChangedIntent,
+                optionsMostRecentPolicyForPackageGreen));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(redPackageChangedIntent,
+                optionsMostRecentPolicyForPackageRed));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageComponentsChangedIntent1,
+                optionsMergedPolicyForPackageGreen));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(redPackageComponentsChangedIntent,
+                optionsMergedPolicyForPackageRed));
+        // Since this broadcast has DELIVERY_GROUP_MOST_RECENT policy set, the earlier
+        // greenPackageChangedIntent broadcast with the same policy will be discarded.
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageChangedIntent,
+                optionsMostRecentPolicyForPackageGreen));
+        // Since this broadcast has DELIVERY_GROUP_MERGED policy set, the earlier
+        // greenPackageComponentsChangedIntent1 broadcast with the same policy will be merged
+        // with this one and then will be discarded.
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(greenPackageComponentsChangedIntent2,
+                optionsMergedPolicyForPackageGreen));
+
+        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        // The extra EXTRA_CHANGED_COMPONENT_NAME_LIST values from
+        // greenPackageComponentsChangedIntent1 and
+        // greenPackageComponentsChangedIntent2 broadcasts would be merged, since
+        // STRATEGY_ARRAY_APPEND was used for this extra.
+        final Intent expectedGreenPackageComponentsChangedIntent = createPackageChangedIntent(
+                getUidForPackage(PACKAGE_GREEN), List.of(PACKAGE_GREEN + ".comp3",
+                        PACKAGE_GREEN + ".comp1", PACKAGE_GREEN + ".comp2"));
+        verifyPendingRecords(queue, List.of(redPackageChangedIntent,
+                redPackageComponentsChangedIntent, greenPackageChangedIntent,
+                expectedGreenPackageComponentsChangedIntent));
+    }
+
     private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs,
             int droppedCount) {
         final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index f79cb11..360d6eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,6 +19,11 @@
 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.multiuser.Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
+import static android.multiuser.Flags.FLAG_LOGOUT_USER_API;
+import static android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
 import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -54,7 +59,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
-import android.multiuser.Flags;
 import android.os.PowerManager;
 import android.os.ServiceSpecificException;
 import android.os.SystemProperties;
@@ -401,15 +405,27 @@
     }
 
     @Test
-    public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
+    public void testGetBootUser_CannotSwitchToHeadlessSystemUser_ThrowsIfOnlySystemUserExists()
+            throws Exception {
         setSystemUserHeadless(true);
         removeNonSystemUsers();
+        mockCanSwitchToHeadlessSystemUser(false);
 
         assertThrows(UserManager.CheckedUserOperationException.class,
                 () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
     @Test
+    public void testGetBootUser_CanSwitchToHeadlessSystemUser_NoThrowIfOnlySystemUserExists()
+            throws Exception {
+        setSystemUserHeadless(true);
+        removeNonSystemUsers();
+        mockCanSwitchToHeadlessSystemUser(true);
+
+        assertThat(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(UserHandle.USER_SYSTEM);
+    }
+
+    @Test
     public void testGetPreviousFullUserToEnterForeground() throws Exception {
         addUser(USER_ID);
         setLastForegroundTime(USER_ID, 1_000_000L);
@@ -601,9 +617,8 @@
     }
 
     @Test
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testAutoLockPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         int mainUser = mUms.getMainUserId();
         assumeTrue(mUms.canAddPrivateProfile(mainUser));
         UserManagerService mSpiedUms = spy(mUms);
@@ -622,10 +637,12 @@
     }
 
     @Test
+    @EnableFlags({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+        FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+    })
     public void testAutoLockOnDeviceLockForPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         int mainUser = mUms.getMainUserId();
         assumeTrue(mUms.canAddPrivateProfile(mainUser));
         UserManagerService mSpiedUms = spy(mUms);
@@ -645,10 +662,12 @@
     }
 
     @Test
+    @EnableFlags({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+        FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+    })
     public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         assumeTrue(mUms.canAddPrivateProfile(0));
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
@@ -665,10 +684,9 @@
     }
 
     @Test
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @DisableFlags(FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE)
     public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         int mainUser = mUms.getMainUserId();
         assumeTrue(mUms.canAddPrivateProfile(mainUser));
         UserManagerService mSpiedUms = spy(mUms);
@@ -687,10 +705,12 @@
     }
 
     @Test
+    @EnableFlags({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+        FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+    })
     public void testAutoLockAfterInactityForPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         int mainUser = mUms.getMainUserId();
         assumeTrue(mUms.canAddPrivateProfile(mainUser));
         UserManagerService mSpiedUms = spy(mUms);
@@ -711,11 +731,12 @@
     }
 
     @Test
+    @EnableFlags({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+        FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+    })
     public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
-
         mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
                 Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
 
@@ -726,10 +747,12 @@
     }
 
     @Test
+    @EnableFlags({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+        FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE
+    })
     public void testSetOrUpdateAutoLockPreference() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         int mainUser = mUms.getMainUserId();
         assumeTrue(mUms.canAddPrivateProfile(mainUser));
         mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
@@ -780,10 +803,12 @@
     }
 
     @Test
+    @EnableFlags({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
+        android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES
+    })
     public void testGetProfileIdsExcludingHidden() {
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
         assumeTrue(mUms.canAddPrivateProfile(0));
         UserInfo privateProfileUser =
                 mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
@@ -794,8 +819,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-            Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @RequiresFlagsEnabled({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+    })
     public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
         UserManagerService mSpiedUms = spy(mUms);
         assumeTrue(mUms.isHeadlessSystemUserMode());
@@ -807,8 +835,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-            Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @RequiresFlagsEnabled({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+    })
     public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
         assumeTrue(mUms.canAddMoreUsersOfType(USER_TYPE_FULL_SECONDARY));
         UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
@@ -819,8 +850,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-            Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @RequiresFlagsEnabled({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+    })
     public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -831,8 +865,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-            Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @RequiresFlagsEnabled({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+    })
     public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -843,8 +880,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-            Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @RequiresFlagsEnabled({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+    })
     public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -855,8 +895,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-            Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
+    @RequiresFlagsEnabled({
+        FLAG_ALLOW_PRIVATE_PROFILE,
+        FLAG_BLOCK_PRIVATE_SPACE_CREATION,
+        FLAG_ENABLE_PRIVATE_SPACE_FEATURES
+    })
     public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
         doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
         int mainUser = mUms.getMainUserId();
@@ -910,7 +953,7 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+    @EnableFlags(FLAG_LOGOUT_USER_API)
     public void testGetUserLogoutability_HsumAndInteractiveHeadlessSystem_UserCanLogout()
             throws Exception {
         setSystemUserHeadless(true);
@@ -926,7 +969,7 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+    @EnableFlags(FLAG_LOGOUT_USER_API)
     public void testGetUserLogoutability_HsumAndNonInteractiveHeadlessSystem_UserCannotLogout()
             throws Exception {
         setSystemUserHeadless(true);
@@ -941,7 +984,7 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+    @EnableFlags(FLAG_LOGOUT_USER_API)
     public void testGetUserLogoutability_Hsum_SystemUserCannotLogout() throws Exception {
         setSystemUserHeadless(true);
         mockCurrentUser(UserHandle.USER_SYSTEM);
@@ -950,7 +993,7 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+    @EnableFlags(FLAG_LOGOUT_USER_API)
     public void testGetUserLogoutability_NonHsum_SystemUserCannotLogout() throws Exception {
         setSystemUserHeadless(false);
         mockCurrentUser(UserHandle.USER_SYSTEM);
@@ -960,7 +1003,7 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+    @EnableFlags(FLAG_LOGOUT_USER_API)
     public void testGetUserLogoutability_CannotSwitch_CannotLogout() throws Exception {
         setSystemUserHeadless(true);
         addUser(USER_ID);
@@ -973,7 +1016,7 @@
     }
 
     @Test
-    @DisableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+    @DisableFlags(FLAG_LOGOUT_USER_API)
     public void testGetUserLogoutability_LogoutDisabled() throws Exception {
         assertThrows(UnsupportedOperationException.class, () -> mUms.getUserLogoutability(USER_ID));
     }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index 7b8824c..00cc726 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -18,6 +18,11 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -59,11 +64,25 @@
     private LinearLayout mDragButton;
     private LinearLayout mScrollButton;
 
+    private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+
+    private final ClickPanelControllerInterface clickPanelController =
+            new ClickPanelControllerInterface() {
+                @Override
+                public void handleAutoclickTypeChange(@AutoclickType int clickType) {
+                    mActiveClickType = clickType;
+                }
+
+                @Override
+                public void toggleAutoclickPause() {}
+            };
+
     @Before
     public void setUp() {
         mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
 
-        mAutoclickTypePanel = new AutoclickTypePanel(mTestableContext, mMockWindowManager);
+        mAutoclickTypePanel =
+                new AutoclickTypePanel(mTestableContext, mMockWindowManager, clickPanelController);
         View contentView = mAutoclickTypePanel.getContentViewForTesting();
         mLeftClickButton = contentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
         mRightClickButton =
@@ -136,6 +155,17 @@
         verifyButtonHasSelectedStyle(mScrollButton);
     }
 
+    @Test
+    public void togglePanelExpansion_selectButton_correctActiveClickType() {
+        // By first click, the panel is expanded.
+        mLeftClickButton.callOnClick();
+
+        // Clicks any button in the expanded state to select a type button.
+        mScrollButton.callOnClick();
+
+        assertThat(mActiveClickType).isEqualTo(AUTOCLICK_TYPE_SCROLL);
+    }
+
     private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
         GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
         assertThat(gradientDrawable.getColor().getDefaultColor())
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 17f5ebb..7349c5f4 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -116,6 +116,10 @@
         deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
     }
 
+    static {
+        System.loadLibrary("servicestestjni");
+    }
+
     @Test
     public void testReadGameServiceSettings() {
         writeOldFiles();
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 04d0752..a4e77c0 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,10 +20,12 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
 import android.hardware.contexthub.ErrorCode;
 import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier;
@@ -32,6 +34,7 @@
 import android.hardware.contexthub.IContextHubEndpointCallback;
 import android.hardware.contexthub.IEndpointCommunication;
 import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -61,6 +64,9 @@
     private static final int ENDPOINT_ID = 1;
     private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";
 
+    private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
+    private static final int TARGET_ENDPOINT_ID = 1;
+
     private ContextHubClientManager mClientManager;
     private ContextHubEndpointManager mEndpointManager;
     private HubInfoRegistry mHubInfoRegistry;
@@ -95,23 +101,8 @@
 
     @Test
     public void testRegisterEndpoint() throws RemoteException {
-        // Register an endpoint and confirm we can get a valid IContextHubEndoint reference
-        HubEndpointInfo info =
-                new HubEndpointInfo(
-                        ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
-        IContextHubEndpoint endpoint =
-                mEndpointManager.registerEndpoint(
-                        info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
-        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
-        assertThat(endpoint).isNotNull();
-        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
-        assertThat(assignedInfo).isNotNull();
-        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
-        assertThat(assignedIdentifier).isNotNull();
-
-        // Unregister the endpoint and confirm proper clean-up
-        mEndpointManager.unregisterEndpoint(assignedIdentifier.getEndpoint());
-        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+        unregisterExampleEndpoint(endpoint);
     }
 
     @Test
@@ -146,4 +137,107 @@
         assertThat(statusCaptor.getValue().messageSequenceNumber).isEqualTo(sequenceNumber);
         assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.DESTINATION_NOT_FOUND);
     }
+
+    @Test
+    public void testHalRestart() throws RemoteException {
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        // Verify that the endpoint is still registered after a HAL restart
+        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+        mEndpointManager.onHalRestart();
+        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+        verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
+        assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+        assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
+    public void testHalRestartOnOpenSession() throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+        mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);
+
+        mEndpointManager.onHalRestart();
+
+        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+        verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
+        assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+        assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+
+        verify(mMockCallback)
+                .onSessionClosed(
+                        sessionId, ContextHubServiceUtil.toAppHubEndpointReason(Reason.HUB_RESET));
+
+        unregisterExampleEndpoint(endpoint);
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+    }
+
+    @Test
+    public void testOpenSessionOnUnregistration() throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+        mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);
+
+        unregisterExampleEndpoint(endpoint);
+        verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.ENDPOINT_GONE);
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+    }
+
+    private IContextHubEndpoint registerExampleEndpoint() throws RemoteException {
+        HubEndpointInfo info =
+                new HubEndpointInfo(
+                        ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
+        IContextHubEndpoint endpoint =
+                mEndpointManager.registerEndpoint(
+                        info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
+        assertThat(endpoint).isNotNull();
+        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
+        assertThat(assignedInfo).isNotNull();
+        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
+        assertThat(assignedIdentifier).isNotNull();
+
+        // Confirm registerEndpoint was called with the right contents
+        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+        verify(mMockEndpointCommunications).registerEndpoint(statusCaptor.capture());
+        assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
+        assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
+        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
+
+        return endpoint;
+    }
+
+    private void unregisterExampleEndpoint(IContextHubEndpoint endpoint) throws RemoteException {
+        HubEndpointInfo expectedInfo = endpoint.getAssignedHubEndpointInfo();
+        endpoint.unregister();
+        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
+        verify(mMockEndpointCommunications).unregisterEndpoint(statusCaptor.capture());
+        assertThat(statusCaptor.getValue().id.id)
+                .isEqualTo(expectedInfo.getIdentifier().getEndpoint());
+        assertThat(statusCaptor.getValue().id.hubId)
+                .isEqualTo(expectedInfo.getIdentifier().getHub());
+        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index 770712a..4101192 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -204,7 +204,7 @@
                 .build());
         final Notification n = new Notification.Builder(getContext())
                 .setContentTitle("foo")
-                .setCategory(CATEGORY_ALARM)
+                .setCategory(new String("alarm"))
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .build();
         NotificationRecord r = getRecord(channel, n);
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index c6b431c..8e2cea7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -402,9 +402,6 @@
 
     @Test
     @EnableFlags({com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
-            com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
-            com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
-            com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
             com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS})
     @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
     public void testKeyboardAccessibilityToggleShortcutPress() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 440f43e..bb29614 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2881,7 +2881,6 @@
         activity2.addStartingWindow(mPackageName, android.R.style.Theme, activity1, true, true,
                 false, true, false, false, false);
         waitUntilHandlersIdle();
-        assertFalse(mDisplayContent.mSkipAppTransitionAnimation);
         assertNoStartingWindow(activity1);
         assertHasStartingWindow(activity2);
     }
@@ -2965,7 +2964,6 @@
                 false /* newTask */, false /* isTaskSwitch */, null /* options */,
                 null /* sourceRecord */);
 
-        assertTrue(mDisplayContent.mSkipAppTransitionAnimation);
         assertNull(middle.mStartingWindow);
         assertHasStartingWindow(top);
         assertTrue(top.isVisible());
@@ -3265,26 +3263,6 @@
                 > activity.getConfiguration().windowConfiguration.getAppBounds().height());
     }
 
-    @Test
-    public void testSetVisibility_visibleToVisible() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true).build();
-        // By default, activity is visible.
-        assertTrue(activity.isVisible());
-        assertTrue(activity.isVisibleRequested());
-        assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-
-        // Request the activity to be visible. Although the activity is already visible, app
-        // transition animation should be applied on this activity. This might be unnecessary, but
-        // until we verify no logic relies on this behavior, we'll keep this as is.
-        mDisplayContent.prepareAppTransition(0);
-        activity.setVisibility(true);
-        assertTrue(activity.isVisible());
-        assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
-        assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-    }
-
     @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testSetVisibility_visibleToInvisible() {
@@ -3316,50 +3294,30 @@
     public void testSetVisibility_invisibleToVisible() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true).setVisible(false).build();
-        // Activiby is invisible. However ATMS requests it to become visible, since this is a top
-        // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.isVisibleRequested());
-        assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+        assertFalse(activity.isVisibleRequested());
 
         // Request the activity to be visible. Since the visibility changes, app transition
         // animation should be applied on this activity.
-        activity.setVisibility(true);
+        requestTransition(activity, WindowManager.TRANSIT_OPEN);
+        mWm.mRoot.resumeFocusedTasksTopActivities();
         assertFalse(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
-        assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
-
-        // There should still be animation (add to opening) if keyguard is going away while the
-        // screen is off because it will be visible after screen is turned on by unlocking.
-        mDisplayContent.mOpeningApps.remove(activity);
-        mDisplayContent.mClosingApps.remove(activity);
-        activity.commitVisibility(false /* visible */, false /* performLayout */);
-        mDisplayContent.getDisplayPolicy().screenTurnedOff(false /* acquireSleepToken */);
-        final KeyguardController controller = mSupervisor.getKeyguardController();
-        doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
-        activity.setVisibility(true);
-        assertTrue(mDisplayContent.mOpeningApps.contains(activity));
+        assertTrue(activity.inTransition());
     }
 
     @Test
     public void testSetVisibility_invisibleToInvisible() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true).setVisible(false).build();
-        // Activiby is invisible. However ATMS requests it to become visible, since this is a top
-        // activity.
-        assertFalse(activity.isVisible());
-        assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
-        assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+        requestTransition(activity, WindowManager.TRANSIT_CLOSE);
 
         // Request the activity to be invisible. Since the activity is already invisible, no app
         // transition should be applied on this activity.
         activity.setVisibility(false);
         assertFalse(activity.isVisible());
         assertFalse(activity.isVisibleRequested());
-        assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
-        assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+        assertFalse(activity.inTransition());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
deleted file mode 100644
index 8871056..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for the {@link ActivityStack} class.
- *
- * Build/Install/Run:
- *  atest WmTests:AnimatingActivityRegistryTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AnimatingActivityRegistryTest extends WindowTestsBase {
-
-    @Mock
-    AnimationAdapter mAdapter;
-
-    @Mock
-    Runnable mMockEndDeferFinishCallback1;
-    @Mock
-    Runnable mMockEndDeferFinishCallback2;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testDeferring() {
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        final ActivityRecord activity2 = createAppWindow(activity1.getTask(), ACTIVITY_TYPE_STANDARD,
-                "activity2").mActivityRecord;
-        final AnimatingActivityRegistry registry =
-                activity1.getRootTask().getAnimatingActivityRegistry();
-
-        activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
-                ANIMATION_TYPE_APP_TRANSITION);
-        activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
-                ANIMATION_TYPE_APP_TRANSITION);
-        assertTrue(activity1.isAnimating(TRANSITION));
-        assertTrue(activity2.isAnimating(TRANSITION));
-
-        // Make sure that first animation finish is deferred, second one is not deferred, and first
-        // one gets cancelled.
-        assertTrue(registry.notifyAboutToFinish(activity1, mMockEndDeferFinishCallback1));
-        assertFalse(registry.notifyAboutToFinish(activity2, mMockEndDeferFinishCallback2));
-        verify(mMockEndDeferFinishCallback1).run();
-        verifyZeroInteractions(mMockEndDeferFinishCallback2);
-    }
-
-    @Test
-    public void testContainerRemoved() {
-        final ActivityRecord window1 = createActivityRecord(mDisplayContent);
-        final ActivityRecord window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD,
-                "window2").mActivityRecord;
-        final AnimatingActivityRegistry registry =
-                window1.getRootTask().getAnimatingActivityRegistry();
-
-        window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
-                ANIMATION_TYPE_APP_TRANSITION);
-        window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
-                ANIMATION_TYPE_APP_TRANSITION);
-        assertTrue(window1.isAnimating(TRANSITION));
-        assertTrue(window2.isAnimating(TRANSITION));
-
-        // Make sure that first animation finish is deferred, and removing the second window stops
-        // finishes all pending deferred finishings.
-        registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1);
-        window2.setParent(null);
-        verify(mMockEndDeferFinishCallback1).run();
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
deleted file mode 100644
index c294bc6..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.gui.DropInputMode;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-import android.window.ITaskFragmentOrganizer;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Build/Install/Run:
- *  atest WmTests:AppTransitionControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppTransitionControllerTest extends WindowTestsBase {
-
-    private AppTransitionController mAppTransitionController;
-
-    @Before
-    public void setUp() throws Exception {
-        assumeFalse(WindowManagerService.sEnableShellTransitions);
-        mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
-        mWm.mAnimator.ready();
-    }
-
-    @Test
-    public void testSkipOccludedActivityCloseTransition() {
-        final ActivityRecord behind = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final ActivityRecord topOpening = createActivityRecord(behind.getTask());
-        topOpening.setOccludesParent(true);
-        topOpening.setVisible(true);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
-        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
-        mDisplayContent.mClosingApps.add(behind);
-
-        assertEquals(WindowManager.TRANSIT_OLD_UNSET,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null, null, false));
-    }
-
-    @Test
-    public void testClearTaskSkipAppExecuteTransition() {
-        final ActivityRecord behind = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final Task task = behind.getTask();
-        final ActivityRecord top = createActivityRecord(task);
-        top.setState(ActivityRecord.State.RESUMED, "test");
-        behind.setState(ActivityRecord.State.STARTED, "test");
-        behind.setVisibleRequested(true);
-
-        task.removeActivities("test", false /* excludingTaskOverlay */);
-        assertFalse(mDisplayContent.mAppTransition.isReady());
-    }
-
-    @Test
-    public void testTranslucentOpen() {
-        final ActivityRecord behind = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        doReturn(false).when(translucentOpening).fillsParent();
-        translucentOpening.setVisible(false);
-        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
-        mDisplayContent.mOpeningApps.add(behind);
-        mDisplayContent.mOpeningApps.add(translucentOpening);
-
-        assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null, null, false));
-    }
-
-    @Test
-    public void testTranslucentClose() {
-        final ActivityRecord behind = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final ActivityRecord translucentClosing = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        doReturn(false).when(translucentClosing).fillsParent();
-        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
-        mDisplayContent.mClosingApps.add(translucentClosing);
-        assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null, null, false));
-    }
-
-    @Test
-    public void testDreamActivityOpenTransition() {
-        final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
-        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
-        mDisplayContent.mOpeningApps.add(dreamActivity);
-
-        assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null, null, false));
-    }
-
-    @Test
-    public void testDreamActivityCloseTransition() {
-        final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
-        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
-        mDisplayContent.mClosingApps.add(dreamActivity);
-
-        assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_CLOSE,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null, null, false));
-    }
-
-    @Test
-    public void testChangeIsNotOverwritten() {
-        final ActivityRecord behind = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        translucentOpening.setOccludesParent(false);
-        translucentOpening.setVisible(false);
-        mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
-        mDisplayContent.mOpeningApps.add(behind);
-        mDisplayContent.mOpeningApps.add(translucentOpening);
-        mDisplayContent.mChangingContainers.add(translucentOpening.getTask());
-        assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null, null, false));
-    }
-
-    @Test
-    public void testTransitWithinTask() {
-        final ActivityRecord opening = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
-        opening.setOccludesParent(false);
-        final ActivityRecord closing = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
-        closing.setOccludesParent(false);
-        final Task task = opening.getTask();
-        mDisplayContent.mOpeningApps.add(opening);
-        mDisplayContent.mClosingApps.add(closing);
-        assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_ACTIVITY_OPEN, task));
-        closing.getTask().removeChild(closing);
-        task.addChild(closing, 0);
-        assertTrue(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_ACTIVITY_OPEN, task));
-        assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_OLD_TASK_OPEN, task));
-    }
-
-
-    @Test
-    public void testIntraWallpaper_open() {
-        final ActivityRecord opening = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        opening.setVisible(false);
-        final WindowManager.LayoutParams attrOpening = new WindowManager.LayoutParams(
-                TYPE_BASE_APPLICATION);
-        attrOpening.setTitle("WallpaperOpening");
-        attrOpening.flags |= FLAG_SHOW_WALLPAPER;
-        final TestWindowState appWindowOpening = createWindowState(attrOpening, opening);
-        opening.addWindow(appWindowOpening);
-
-        final ActivityRecord closing = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final WindowManager.LayoutParams attrClosing = new WindowManager.LayoutParams(
-                TYPE_BASE_APPLICATION);
-        attrOpening.setTitle("WallpaperClosing");
-        attrClosing.flags |= FLAG_SHOW_WALLPAPER;
-        final TestWindowState appWindowClosing = createWindowState(attrClosing, closing);
-        closing.addWindow(appWindowClosing);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
-        mDisplayContent.mOpeningApps.add(opening);
-        mDisplayContent.mClosingApps.add(closing);
-
-        assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, appWindowClosing, null, false));
-    }
-
-    @Test
-    public void testIntraWallpaper_toFront() {
-        final ActivityRecord opening = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        opening.setVisible(false);
-        final WindowManager.LayoutParams attrOpening = new WindowManager.LayoutParams(
-                TYPE_BASE_APPLICATION);
-        attrOpening.setTitle("WallpaperOpening");
-        attrOpening.flags |= FLAG_SHOW_WALLPAPER;
-        final TestWindowState appWindowOpening = createWindowState(attrOpening, opening);
-        opening.addWindow(appWindowOpening);
-
-        final ActivityRecord closing = createActivityRecord(mDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        final WindowManager.LayoutParams attrClosing = new WindowManager.LayoutParams(
-                TYPE_BASE_APPLICATION);
-        attrOpening.setTitle("WallpaperClosing");
-        attrClosing.flags |= FLAG_SHOW_WALLPAPER;
-        final TestWindowState appWindowClosing = createWindowState(attrClosing, closing);
-        closing.addWindow(appWindowClosing);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
-        mDisplayContent.mOpeningApps.add(opening);
-        mDisplayContent.mClosingApps.add(closing);
-
-        assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, appWindowClosing, null, false));
-    }
-
-    @Test
-    public void testGetAnimationTargets_visibilityAlreadyUpdated() {
-        // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
-        //                   +- [Task2] - [ActivityRecord2] (closing, invisible)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(false);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // No animation, since visibility of the opening and closing apps are already updated
-        // outside of AppTransition framework.
-        assertEquals(
-                new ArraySet<>(),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_visibilityAlreadyUpdated_butForcedTransitionRequested() {
-        // [DisplayContent] -+- [Task1] - [ActivityRecord1] (closing, invisible)
-        //                   +- [Task2] - [ActivityRecord2] (opening, visible)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        activity1.setVisible(true);
-        activity1.setVisibleRequested(true);
-        activity1.mRequestForceTransition = true;
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(false);
-        activity2.mRequestForceTransition = true;
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // The visibility are already updated, but since forced transition is requested, it will
-        // be included.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity2.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_exitingBeforeTransition() {
-        // Create another non-empty task so the animation target won't promote to task display area.
-        createActivityRecord(mDisplayContent);
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        activity.setVisible(false);
-        activity.mIsExiting = true;
-
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity);
-
-        // Animate closing apps even if it's not visible when it is exiting before we had a chance
-        // to play the transition animation.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        new ArraySet<>(), closing, false /* visible */));
-    }
-
-    @Test
-    public void testExitAnimationDone_beforeAppTransition() {
-        final Task task = createTask(mDisplayContent);
-        final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win");
-        spyOn(win);
-        win.mAnimatingExit = true;
-        mDisplayContent.mAppTransition.setTimeout();
-        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
-        verify(win).onExitAnimationDone();
-    }
-
-    @Test
-    public void testGetAnimationTargets_openingClosingInDifferentTask() {
-        // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
-        //                   |           +- [ActivityRecord2] (invisible)
-        //                   |
-        //                   +- [Task2] -+- [ActivityRecord3] (closing, visible)
-        //                               +- [ActivityRecord4] (invisible)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
-                activity1.getTask());
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(false);
-
-        final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
-        final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
-                activity3.getTask());
-        activity4.setVisible(false);
-        activity4.setVisibleRequested(false);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity3);
-
-        // Promote animation targets to root Task level. Invisible ActivityRecords don't affect
-        // promotion decision.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity3.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_openingClosingInSameTask() {
-        // [DisplayContent] - [Task] -+- [ActivityRecord1] (opening, invisible)
-        //                            +- [ActivityRecord2] (closing, visible)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
-                activity1.getTask());
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Don't promote an animation target to Task level, since the same task contains both
-        // opening and closing app.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity2}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_animateOnlyTranslucentApp() {
-        // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
-        //                   |           +- [ActivityRecord2] (visible)
-        //                   |
-        //                   +- [Task2] -+- [ActivityRecord3] (closing, visible)
-        //                               +- [ActivityRecord4] (visible)
-
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        activity1.setOccludesParent(false);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
-                activity1.getTask());
-
-        final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
-        activity3.setOccludesParent(false);
-        final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
-                activity3.getTask());
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity3);
-
-        // Don't promote an animation target to Task level, since opening (closing) app is
-        // translucent and is displayed over other non-animating app.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity3}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_animateTranslucentAndOpaqueApps() {
-        // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
-        //                   |           +- [ActivityRecord2] (opening, invisible)
-        //                   |
-        //                   +- [Task2] -+- [ActivityRecord3] (closing, visible)
-        //                               +- [ActivityRecord4] (closing, visible)
-
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        activity1.setOccludesParent(false);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
-                activity1.getTask());
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-
-        final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
-        activity3.setOccludesParent(false);
-        final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
-                activity3.getTask());
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        opening.add(activity2);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity3);
-        closing.add(activity4);
-
-        // Promote animation targets to TaskStack level even though opening (closing) app is
-        // translucent as long as all visible siblings animate at the same time.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity3.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_taskContainsMultipleTasks() {
-        // [DisplayContent] - [Task] -+- [Task1] - [ActivityRecord1] (opening, invisible)
-        //                            +- [Task2] - [ActivityRecord2] (closing, visible)
-        final Task parentTask = createTask(mDisplayContent);
-        final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Promote animation targets up to Task level, not beyond.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_splitScreenOpening() {
-        // [DisplayContent] - [Task] -+- [split task 1] -+- [Task1] - [AR1] (opening, invisible)
-        //                            +- [split task 2] -+- [Task2] - [AR2] (opening, invisible)
-        final Task singleTopRoot = createTask(mDisplayContent);
-        final TaskBuilder builder = new TaskBuilder(mSupervisor)
-                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
-                .setParentTask(singleTopRoot)
-                .setCreatedByOrganizer(true);
-        final Task splitRoot1 = builder.build();
-        final Task splitRoot2 = builder.build();
-        splitRoot1.setAdjacentTaskFragment(splitRoot2);
-        final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        opening.add(activity2);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-
-        // Promote animation targets up to Task level, not beyond.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{splitRoot1, splitRoot2}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_openingClosingTaskFragment() {
-        // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
-        //                        +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
-        final Task parentTask = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
-        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-
-        final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
-        final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
-        activity2.setVisible(true);
-        activity2.setVisibleRequested(false);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Promote animation targets up to TaskFragment level, not beyond.
-        assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_openingTheOnlyTaskFragmentInTask() {
-        // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (opening, invisible)
-        //               +- [Task2] - [ActivityRecord2] (closing, visible)
-        final Task task1 = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
-        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
-        activity2.setVisible(true);
-        activity2.setVisibleRequested(false);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Promote animation targets up to leaf Task level because there's only one TaskFragment in
-        // the Task.
-        assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_closingTheOnlyTaskFragmentInTask() {
-        // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (closing, visible)
-        //               +- [Task2] - [ActivityRecord2] (opening, invisible)
-        final Task task1 = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
-        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
-        activity1.setVisible(true);
-        activity1.setVisibleRequested(false);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity2);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity1);
-
-        // Promote animation targets up to leaf Task level because there's only one TaskFragment in
-        // the Task.
-        assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_embeddedTask() {
-        // [DisplayContent] -+- [Task1] -            [ActivityRecord1] (opening, invisible)
-        //                   +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-
-        final Task task2 = createTask(mDisplayContent);
-        task2.mRemoveWithTaskOrganizer = true;
-        final ActivityRecord activity2 = createActivityRecord(task2);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        opening.add(activity2);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-
-        // No animation on the embedded task.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-
-    @Test
-    public void testGetAnimationTargets_activityInEmbeddedTask() {
-        // [DisplayContent] - [Task] (embedded)-+- [ActivityRecord1] (opening, invisible)
-        //                                      +- [ActivityRecord2] (closing, visible)
-        final Task task = createTask(mDisplayContent);
-        task.mRemoveWithTaskOrganizer = true;
-
-        final ActivityRecord activity1 = createActivityRecord(task);
-        activity1.setVisible(false);
-        activity1.setVisibleRequested(true);
-        final ActivityRecord activity2 = createActivityRecord(task);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Even though embedded task itself doesn't animate, activities in an embedded task
-        // animate.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity2}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    static class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
-        private IRemoteAnimationFinishedCallback mFinishedCallback;
-
-        @Override
-        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-            mFinishedCallback = finishedCallback;
-        }
-
-        @Override
-        public void onAnimationCancelled() throws RemoteException {
-            mFinishedCallback = null;
-        }
-
-        @Override
-        public IBinder asBinder() {
-            return new Binder();
-        }
-
-        boolean isAnimationStarted() {
-            return mFinishedCallback != null;
-        }
-
-        void finishAnimation() {
-            try {
-                mFinishedCallback.onAnimationFinished();
-            } catch (RemoteException e) {
-                fail();
-            }
-        }
-    }
-
-    @Test
-    public void testGetRemoteAnimationOverrideEmpty() {
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        assertNull(mAppTransitionController.getRemoteAnimationOverride(activity,
-                TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
-    }
-
-    @Test
-    public void testGetRemoteAnimationOverrideWindowContainer() {
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
-                new TestRemoteAnimationRunner(), 10, 1);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
-        activity.registerRemoteAnimations(definition);
-
-        assertEquals(adapter,
-                mAppTransitionController.getRemoteAnimationOverride(
-                        activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
-        assertNull(mAppTransitionController.getRemoteAnimationOverride(
-                        null, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
-    }
-
-    @Test
-    public void testGetRemoteAnimationOverrideTransitionController() {
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
-                new TestRemoteAnimationRunner(), 10, 1);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
-        mAppTransitionController.registerRemoteAnimations(definition);
-
-        assertEquals(adapter,
-                mAppTransitionController.getRemoteAnimationOverride(
-                        activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
-        assertEquals(adapter,
-                mAppTransitionController.getRemoteAnimationOverride(
-                        null, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
-    }
-
-    @Test
-    public void testGetRemoteAnimationOverrideBoth() {
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        final RemoteAnimationDefinition definition1 = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter adapter1 = new RemoteAnimationAdapter(
-                new TestRemoteAnimationRunner(), 10, 1);
-        definition1.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter1);
-        activity.registerRemoteAnimations(definition1);
-
-        final RemoteAnimationDefinition definition2 = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter adapter2 = new RemoteAnimationAdapter(
-                new TestRemoteAnimationRunner(), 10, 1);
-        definition2.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, adapter2);
-        mAppTransitionController.registerRemoteAnimations(definition2);
-
-        assertEquals(adapter2,
-                mAppTransitionController.getRemoteAnimationOverride(
-                        activity, TRANSIT_OLD_KEYGUARD_UNOCCLUDE, new ArraySet<Integer>()));
-        assertEquals(adapter2,
-                mAppTransitionController.getRemoteAnimationOverride(
-                        null, TRANSIT_OLD_KEYGUARD_UNOCCLUDE, new ArraySet<Integer>()));
-    }
-
-    @Test
-    public void testGetRemoteAnimationOverrideWindowContainerHasPriority() {
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        final RemoteAnimationDefinition definition1 = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter adapter1 = new RemoteAnimationAdapter(
-                new TestRemoteAnimationRunner(), 10, 1);
-        definition1.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter1);
-        activity.registerRemoteAnimations(definition1);
-
-        final RemoteAnimationDefinition definition2 = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter adapter2 = new RemoteAnimationAdapter(
-                new TestRemoteAnimationRunner(), 10, 1);
-        definition2.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter2);
-        mAppTransitionController.registerRemoteAnimations(definition2);
-
-        assertEquals(adapter1,
-                mAppTransitionController.getRemoteAnimationOverride(
-                        activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord activity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(activity);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
-        final Task task = createTask(mDisplayContent);
-        final ActivityRecord closingActivity = createActivityRecord(task);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
-        // Make sure the TaskFragment is not embedded.
-        assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        prepareActivityForAppTransition(openingActivity);
-        final int uid = 12345;
-        closingActivity.info.applicationInfo.uid = uid;
-        openingActivity.info.applicationInfo.uid = uid;
-        task.effectiveUid = uid;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity,
-                null /* changingTaskFragment */);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation is not run by the remote handler because the activity is filling the Task.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
-        final Task task = createTask(mDisplayContent);
-        final ActivityRecord closingActivity = createActivityRecord(task);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
-        // Make sure the TaskFragment is embedded.
-        taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        final Rect embeddedBounds = new Rect(task.getBounds());
-        embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
-        taskFragment.setBounds(embeddedBounds);
-        assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        prepareActivityForAppTransition(openingActivity);
-        final int uid = 12345;
-        closingActivity.info.applicationInfo.uid = uid;
-        openingActivity.info.applicationInfo.uid = uid;
-        task.effectiveUid = uid;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity,
-                null /* changingTaskFragment */);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing non-embedded activity.
-        final ActivityRecord closingActivity = createActivityRecord(task);
-        prepareActivityForAppTransition(closingActivity);
-        // Opening TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(openingActivity);
-        task.effectiveUid = openingActivity.getUid();
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing TaskFragment with embedded activity.
-        final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        closingActivity.info.applicationInfo.uid = 12345;
-        // Opening TaskFragment with embedded activity with different UID.
-        final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
-        prepareActivityForAppTransition(openingActivity);
-        openingActivity.info.applicationInfo.uid = 54321;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing activity in Task1.
-        final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
-        prepareActivityForAppTransition(closingActivity);
-        // Opening TaskFragment with embedded activity in Task2.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(openingActivity);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation not run by the remote handler.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        closingActivity.info.applicationInfo.uid = 12345;
-        task.effectiveUid = closingActivity.getUid();
-        // Opening non-embedded activity with different UID.
-        final ActivityRecord openingActivity = createActivityRecord(task);
-        prepareActivityForAppTransition(openingActivity);
-        openingActivity.info.applicationInfo.uid = 54321;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation should not run by the remote handler when there are non-embedded activities of
-        // different UID.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord activity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(activity);
-        // Set wallpaper as visible.
-        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
-                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
-        spyOn(mDisplayContent.mWallpaperController);
-        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // Animation should not run by the remote handler when there is wallpaper in the transition.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
-        // one is untrusted embedded.
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(2)
-                .setOrganizer(organizer)
-                .build();
-        final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord();
-        final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord();
-        // Also create a non-embedded activity in the Task.
-        final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
-        task.addChild(activity2, POSITION_BOTTOM);
-        prepareActivityForAppTransition(activity0);
-        prepareActivityForAppTransition(activity1);
-        prepareActivityForAppTransition(activity2);
-        doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0);
-        doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // The animation will be animated remotely by client and all activities are input disabled
-        // for untrusted animation.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-        verify(activity0).setDropInputForAnimation(true);
-        verify(activity1).setDropInputForAnimation(true);
-        verify(activity2).setDropInputForAnimation(true);
-        verify(activity0).setDropInputMode(DropInputMode.ALL);
-        verify(activity1).setDropInputMode(DropInputMode.ALL);
-        verify(activity2).setDropInputMode(DropInputMode.ALL);
-
-        // Reset input after animation is finished.
-        clearInvocations(activity0);
-        clearInvocations(activity1);
-        clearInvocations(activity2);
-        remoteAnimationRunner.finishAnimation();
-
-        verify(activity0).setDropInputForAnimation(false);
-        verify(activity1).setDropInputForAnimation(false);
-        verify(activity2).setDropInputForAnimation(false);
-        verify(activity0).setDropInputMode(DropInputMode.OBSCURED);
-        verify(activity1).setDropInputMode(DropInputMode.NONE);
-        verify(activity2).setDropInputMode(DropInputMode.NONE);
-    }
-
-    /**
-     * Since we don't have any use case to rely on handling input during animation, disable it even
-     * if it is trusted embedding so that it could cover some edge-cases when a previously trusted
-     * host starts doing something bad.
-     */
-    @Test
-    public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with only trusted embedded activity
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(1)
-                .setOrganizer(organizer)
-                .build();
-        final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
-        prepareActivityForAppTransition(activity);
-        doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // The animation will be animated remotely by client and all activities are input disabled
-        // for untrusted animation.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-        verify(activity).setDropInputForAnimation(true);
-        verify(activity).setDropInputMode(DropInputMode.ALL);
-
-        // Reset input after animation is finished.
-        clearInvocations(activity);
-        remoteAnimationRunner.finishAnimation();
-
-        verify(activity).setDropInputForAnimation(false);
-        verify(activity).setDropInputMode(DropInputMode.NONE);
-    }
-
-    /**
-     * We don't need to drop input for fully trusted embedding (system app, and embedding in the
-     * same app). This will allow users to do fast tapping.
-     */
-    @Test
-    public void testOverrideTaskFragmentAdapter_noInputProtectedForFullyTrustedAnimation() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with only trusted embedded activity
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(1)
-                .setOrganizer(organizer)
-                .build();
-        final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
-        prepareActivityForAppTransition(activity);
-        final int uid = mAtm.mTaskFragmentOrganizerController.getTaskFragmentOrganizerUid(
-                getITaskFragmentOrganizer(organizer));
-        doReturn(true).when(task).isFullyTrustedEmbedding(uid);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        waitUntilWindowAnimatorIdle();
-
-        // The animation will be animated remotely by client, but input should not be dropped for
-        // fully trusted.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-        verify(activity, never()).setDropInputForAnimation(true);
-        verify(activity, never()).setDropInputMode(DropInputMode.ALL);
-    }
-
-    @Test
-    public void testTransitionGoodToGoForTaskFragments() {
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final Task task = createTask(mDisplayContent);
-        final TaskFragment changeTaskFragment =
-                createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .setOrganizer(organizer)
-                .build();
-        prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity());
-        spyOn(mDisplayContent.mAppTransition);
-        spyOn(emptyTaskFragment);
-
-        prepareAndTriggerAppTransition(
-                null /* openingActivity */, null /* closingActivity*/, changeTaskFragment);
-
-        // Transition not ready because there is an empty non-finishing TaskFragment.
-        verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
-
-        doReturn(true).when(emptyTaskFragment).hasChild();
-        emptyTaskFragment.remove(false /* withTransition */, "test");
-
-        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
-        // Transition ready because the empty (no running activity) TaskFragment is requested to be
-        // removed.
-        verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
-    }
-
-    @Test
-    public void testTransitionGoodToGoForTaskFragments_detachedApp() {
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
-        registerTaskFragmentOrganizer(iOrganizer);
-        final Task task = createTask(mDisplayContent);
-        final TaskFragment changeTaskFragment =
-                createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .setOrganizer(organizer)
-                .build();
-        prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity());
-        // To make sure that having a detached activity won't cause any issue.
-        final ActivityRecord detachedActivity = createActivityRecord(task);
-        detachedActivity.removeImmediately();
-        assertNull(detachedActivity.getRootTask());
-        spyOn(mDisplayContent.mAppTransition);
-        spyOn(emptyTaskFragment);
-
-        prepareAndTriggerAppTransition(
-                null /* openingActivity */, detachedActivity, changeTaskFragment);
-
-        // Transition not ready because there is an empty non-finishing TaskFragment.
-        verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
-
-        doReturn(true).when(emptyTaskFragment).hasChild();
-        emptyTaskFragment.remove(false /* withTransition */, "test");
-
-        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-
-        // Transition ready because the empty (no running activity) TaskFragment is requested to be
-        // removed.
-        verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
-    }
-
-    /** Registers remote animation for the organizer. */
-    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
-            TestRemoteAnimationRunner remoteAnimationRunner) {
-        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
-                remoteAnimationRunner, 10, 1);
-        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
-        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
-        registerTaskFragmentOrganizer(iOrganizer);
-        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
-    }
-
-    private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
-            TaskFragmentOrganizer organizer) {
-        return ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
-    }
-
-    private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
-            @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) {
-        if (openingActivity != null) {
-            mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
-            mDisplayContent.mOpeningApps.add(openingActivity);
-        }
-        if (closingActivity != null) {
-            mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0);
-            mDisplayContent.mClosingApps.add(closingActivity);
-        }
-        if (changingTaskFragment != null) {
-            mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
-            mDisplayContent.mChangingContainers.add(changingTaskFragment);
-        }
-        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
-    }
-
-    private static void prepareActivityForAppTransition(ActivityRecord activity) {
-        // Transition will wait until all participated activities to be drawn.
-        activity.allDrawn = true;
-        // Skip manipulate the SurfaceControl.
-        doNothing().when(activity).setDropInputMode(anyInt());
-        // Assume the activity contains a window.
-        doReturn(true).when(activity).hasChild();
-        // Make sure activity can create remote animation target.
-        doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
-                any());
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
deleted file mode 100644
index 8553fbd..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_UNSET;
-import static android.view.WindowManager.TRANSIT_OPEN;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.animation.Animation;
-import android.window.ITaskFragmentOrganizer;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.policy.TransitionAnimation;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link AppTransition}.
- *
- * Build/Install/Run:
- *  atest WmTests:AppTransitionTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppTransitionTests extends WindowTestsBase {
-    private DisplayContent mDc;
-
-    @Before
-    public void setUp() throws Exception {
-        doNothing().when(mWm.mRoot).performSurfacePlacement();
-        mDc = mWm.getDefaultDisplayContentLocked();
-    }
-
-    @Test
-    public void testKeyguardOverride() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        final ActivityRecord activity = createActivityRecord(dc);
-
-        mDc.prepareAppTransition(TRANSIT_OPEN);
-        mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
-        mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
-        mDc.mOpeningApps.add(activity);
-        assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testKeyguardUnoccludeOcclude() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        final ActivityRecord activity = createActivityRecord(dc);
-
-        mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
-        mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
-        mDc.mOpeningApps.add(activity);
-        assertEquals(TRANSIT_NONE,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-
-    }
-
-    @Test
-    public void testKeyguardKeep() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        final ActivityRecord activity = createActivityRecord(dc);
-
-        mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
-        mDc.prepareAppTransition(TRANSIT_OPEN);
-        mDc.mOpeningApps.add(activity);
-        assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testCrashing() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        final ActivityRecord activity = createActivityRecord(dc);
-
-        mDc.prepareAppTransition(TRANSIT_OPEN);
-        mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
-        mDc.mClosingApps.add(activity);
-        assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testKeepKeyguard_withCrashing() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        final ActivityRecord activity = createActivityRecord(dc);
-
-        mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
-        mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
-        mDc.mClosingApps.add(activity);
-        assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testSkipTransitionAnimation() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        final ActivityRecord activity = createActivityRecord(dc);
-
-        mDc.prepareAppTransition(TRANSIT_OPEN);
-        mDc.prepareAppTransition(TRANSIT_CLOSE);
-        mDc.mClosingApps.add(activity);
-        assertEquals(TRANSIT_OLD_UNSET,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, true /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testTaskChangeWindowingMode() {
-        final ActivityRecord activity = createActivityRecord(mDc);
-
-        mDc.prepareAppTransition(TRANSIT_OPEN);
-        mDc.prepareAppTransition(TRANSIT_CHANGE);
-        mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
-        mDc.mChangingContainers.add(activity.getTask());
-
-        assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testTaskFragmentChange() {
-        final ActivityRecord activity = createActivityRecord(mDc);
-        final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(),
-                true /* createdByOrganizer */, true /* isEmbedded */);
-        activity.getTask().addChild(taskFragment, POSITION_TOP);
-        activity.reparent(taskFragment, POSITION_TOP);
-
-        mDc.prepareAppTransition(TRANSIT_OPEN);
-        mDc.prepareAppTransition(TRANSIT_CHANGE);
-        mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
-        mDc.mChangingContainers.add(taskFragment);
-
-        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
-                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
-    }
-
-    @Test
-    public void testTaskFragmentOpeningTransition() {
-        final ActivityRecord activity = createHierarchyForTaskFragmentTest();
-        activity.setVisible(false);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
-        mDisplayContent.mOpeningApps.add(activity);
-        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
-    }
-
-    @Test
-    public void testTaskFragmentClosingTransition() {
-        final ActivityRecord activity = createHierarchyForTaskFragmentTest();
-        activity.setVisible(true);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
-        mDisplayContent.mClosingApps.add(activity);
-        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
-    }
-
-    /**
-     * Creates a {@link Task} with two {@link TaskFragment TaskFragments}.
-     * The bottom TaskFragment is to prevent
-     * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation
-     * target} to promote to Task or above.
-     *
-     * @return The Activity to be put in either opening or closing Activity
-     */
-    private ActivityRecord createHierarchyForTaskFragmentTest() {
-        final Task parentTask = createTask(mDisplayContent);
-        final TaskFragment bottomTaskFragment = createTaskFragmentWithActivity(parentTask);
-        final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity();
-        bottomActivity.setOccludesParent(true);
-        bottomActivity.setVisible(true);
-
-        final TaskFragment verifiedTaskFragment = createTaskFragmentWithActivity(parentTask);
-        final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity();
-        activity.setOccludesParent(true);
-
-        return activity;
-    }
-
-    @Test
-    public void testAppTransitionStateForMultiDisplay() {
-        // Create 2 displays & presume both display the state is ON for ready to display & animate.
-        final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
-        final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
-
-        // Create 2 app window tokens to represent 2 activity window.
-        final ActivityRecord activity1 = createActivityRecord(dc1);
-        final ActivityRecord activity2 = createActivityRecord(dc2);
-
-        activity1.allDrawn = true;
-        activity1.startingMoved = true;
-
-        // Simulate activity resume / finish flows to prepare app transition & set visibility,
-        // make sure transition is set as expected for each display.
-        dc1.prepareAppTransition(TRANSIT_OPEN);
-        dc2.prepareAppTransition(TRANSIT_CLOSE);
-        // One activity window is visible for resuming & the other activity window is invisible
-        // for finishing in different display.
-        activity1.setVisibility(true);
-        activity2.setVisibility(false);
-
-        // Make sure each display is in animating stage.
-        assertTrue(dc1.mOpeningApps.size() > 0);
-        assertTrue(dc2.mClosingApps.size() > 0);
-        assertTrue(dc1.isAppTransitioning());
-        assertTrue(dc2.isAppTransitioning());
-    }
-
-    @Test
-    public void testCleanAppTransitionWhenRootTaskReparent() {
-        // Create 2 displays & presume both display the state is ON for ready to display & animate.
-        final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
-        final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
-
-        final Task rootTask1 = createTask(dc1);
-        final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
-        final ActivityRecord activity1 = createNonAttachedActivityRecord(dc1);
-        task1.addChild(activity1, 0);
-
-        // Simulate same app is during opening / closing transition set stage.
-        dc1.mClosingApps.add(activity1);
-        assertTrue(dc1.mClosingApps.size() > 0);
-
-        dc1.prepareAppTransition(TRANSIT_OPEN);
-        assertTrue(dc1.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-        assertTrue(dc1.mAppTransition.isTransitionSet());
-
-        dc1.mOpeningApps.add(activity1);
-        assertTrue(dc1.mOpeningApps.size() > 0);
-
-        // Move root task to another display.
-        rootTask1.reparent(dc2.getDefaultTaskDisplayArea(), true);
-
-        // Verify if token are cleared from both pending transition list in former display.
-        assertFalse(dc1.mOpeningApps.contains(activity1));
-        assertFalse(dc1.mOpeningApps.contains(activity1));
-    }
-
-    @Test
-    public void testLoadAnimationSafely() {
-        DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        assertNull(dc.mAppTransition.loadAnimationSafely(
-                getInstrumentation().getTargetContext(), -1));
-    }
-
-    @Test
-    public void testCancelRemoteAnimationWhenFreeze() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        doReturn(false).when(dc).onDescendantOrientationChanged(any());
-        final WindowState exitingAppWindow = newWindowBuilder("exiting app",
-                TYPE_BASE_APPLICATION).setDisplay(dc).build();
-        final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
-        // Wait until everything in animation handler get executed to prevent the exiting window
-        // from being removed during WindowSurfacePlacer Traversal.
-        waitUntilHandlersIdle();
-
-        // Set a remote animator.
-        final TestRemoteAnimationRunner runner = new TestRemoteAnimationRunner();
-        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
-                runner, 100, 50, true /* changeNeedsSnapshot */);
-        // RemoteAnimationController will tracking RemoteAnimationAdapter's caller with calling pid.
-        adapter.setCallingPidUid(123, 456);
-
-        // Simulate activity finish flows to prepare app transition & set visibility,
-        // make sure transition is set as expected.
-        dc.prepareAppTransition(TRANSIT_CLOSE);
-        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_CLOSE));
-        dc.mAppTransition.overridePendingAppTransitionRemote(adapter);
-        exitingActivity.setVisibility(false);
-        assertTrue(dc.mClosingApps.size() > 0);
-
-        // Make sure window is in animating stage before freeze, and cancel after freeze.
-        assertTrue(dc.isAppTransitioning());
-        assertFalse(runner.mCancelled);
-        dc.mAppTransition.freeze();
-        assertFalse(dc.isAppTransitioning());
-        assertTrue(runner.mCancelled);
-    }
-
-    @Test
-    public void testGetAnimationStyleResId() {
-        // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
-        // specifying window type.
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-        attrs.windowAnimations = 0x12345678;
-        assertEquals(attrs.windowAnimations, mDc.mAppTransition.getAnimationStyleResId(attrs));
-
-        // Verify getAnimationStyleResId will return system resource Id when the window type is
-        // starting window.
-        attrs.type = TYPE_APPLICATION_STARTING;
-        assertEquals(mDc.mAppTransition.getDefaultWindowAnimationStyleResId(),
-                mDc.mAppTransition.getAnimationStyleResId(attrs));
-    }
-
-    @Test
-    public void testActivityRecordReparentedToTaskFragment() {
-        final ActivityRecord activity = createActivityRecord(mDc);
-        final SurfaceControl activityLeash = mock(SurfaceControl.class);
-        doNothing().when(activity).setDropInputMode(anyInt());
-        activity.setVisibility(true);
-        activity.setSurfaceControl(activityLeash);
-        final Task task = activity.getTask();
-
-        // Add a TaskFragment of half of the Task size.
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final ITaskFragmentOrganizer iOrganizer =
-                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
-        registerTaskFragmentOrganizer(iOrganizer);
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .setOrganizer(organizer)
-                .build();
-        final Rect taskBounds = new Rect();
-        task.getBounds(taskBounds);
-        taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
-        spyOn(taskFragment);
-
-        assertTrue(mDc.mChangingContainers.isEmpty());
-        assertFalse(mDc.mAppTransition.isTransitionSet());
-
-        // Schedule app transition when reparent activity to a TaskFragment of different size.
-        final Rect startBounds = new Rect(activity.getBounds());
-        activity.reparent(taskFragment, POSITION_TOP);
-
-        // It should transit at TaskFragment level with snapshot on the activity surface.
-        verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
-        assertTrue(mDc.mChangingContainers.contains(taskFragment));
-        assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
-    }
-
-    @Test
-    public void testGetNextAppTransitionBackgroundColor() {
-        assumeFalse(WindowManagerService.sEnableShellTransitions);
-
-        // No override by default.
-        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
-
-        // Override with a custom color.
-        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
-        final int testColor = 123;
-        mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
-                0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
-                false /* overrideTaskTransaction */);
-
-        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
-        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
-        // Override with ActivityEmbedding remote animation. Background color should be kept.
-        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
-                false /* sync */, true /* isActivityEmbedding */);
-
-        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
-        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
-        // Background color should not be cleared anymore after #clear().
-        mDc.mAppTransition.clear();
-        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
-        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-    }
-
-    @Test
-    public void testGetNextAppRequestedAnimation() {
-        assumeFalse(WindowManagerService.sEnableShellTransitions);
-        final String packageName = "testPackage";
-        final int enterAnimResId = 1;
-        final int exitAnimResId = 2;
-        final int testColor = 123;
-        final Animation enterAnim = mock(Animation.class);
-        final Animation exitAnim = mock(Animation.class);
-        final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
-        spyOn(transitionAnimation);
-        doReturn(enterAnim).when(transitionAnimation)
-                .loadAppTransitionAnimation(packageName, enterAnimResId);
-        doReturn(exitAnim).when(transitionAnimation)
-                .loadAppTransitionAnimation(packageName, exitAnimResId);
-
-        // No override by default.
-        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
-        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
-
-        // Override with a custom animation.
-        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
-        mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
-                testColor, null /* startedCallback */, null /* endedCallback */,
-                false /* overrideTaskTransaction */);
-
-        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
-        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
-        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
-        // Override with ActivityEmbedding remote animation. Custom animation should be kept.
-        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
-                false /* sync */, true /* isActivityEmbedding */);
-
-        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
-        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
-        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
-
-        // Custom animation should not be cleared anymore after #clear().
-        mDc.mAppTransition.clear();
-        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
-        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
-    }
-
-    private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
-        boolean mCancelled = false;
-        @Override
-        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
-                RemoteAnimationTarget[] apps,
-                RemoteAnimationTarget[] wallpapers,
-                RemoteAnimationTarget[] nonApps,
-                IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-        }
-
-        @Override
-        public void onAnimationCancelled() {
-            mCancelled = true;
-        }
-
-        @Override
-        public IBinder asBinder() {
-            return null;
-        }
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 0964ebe..82435b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1172,11 +1172,12 @@
                 .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
         prev.setVisibleRequested(false);
         final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false)
                 .setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
         assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
                 mDisplayContent.rotationForActivityInDifferentOrientation(top));
 
-        mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+        requestTransition(top, WindowManager.TRANSIT_OPEN);
         top.setVisibility(true);
         mDisplayContent.updateOrientation();
         // The top uses "behind", so the orientation is decided by the previous.
@@ -1609,8 +1610,7 @@
         final ActivityRecord app = mAppWindow.mActivityRecord;
         app.setVisible(false);
         app.setVisibleRequested(false);
-        registerTestTransitionPlayer();
-        mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+        requestTransition(app, WindowManager.TRANSIT_OPEN);
         app.setVisibility(true);
         final int newOrientation = getRotatedOrientation(mDisplayContent);
         app.setRequestedOrientation(newOrientation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
new file mode 100644
index 0000000..db90c28
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 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.view.Display.FLAG_PRESENTATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.graphics.Rect;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.IWindow;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:PresentationControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PresentationControllerTests extends WindowTestsBase {
+
+    @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+    @Test
+    public void testPresentationHidesActivitiesBehind() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.copyFrom(mDisplayInfo);
+        displayInfo.flags = FLAG_PRESENTATION;
+        final DisplayContent dc = createNewDisplay(displayInfo);
+        final int displayId = dc.getDisplayId();
+        doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+        final ActivityRecord activity = createActivityRecord(createTask(dc));
+        assertTrue(activity.isVisible());
+
+        doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+        final int uid = 100000; // uid for non-system user
+        final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
+        final int userId = UserHandle.getUserId(uid);
+        doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_PRESENTATION);
+
+        final IWindow clientWindow = new TestIWindow();
+        final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+                userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
+                new InsetsSourceControl.Array(), new Rect(), new float[1]);
+        assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
+        assertFalse(activity.isVisible());
+
+        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
+        window.removeImmediately();
+        assertTrue(activity.isVisible());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index fc4f54a..e4a1bf6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -454,31 +454,6 @@
     }
 
     @Test
-    public void testMovingBottomMostRootTaskActivityToPinnedRootTask() {
-        final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
-                .setTask(fullscreenTask).build();
-        final Task task = firstActivity.getTask();
-
-        final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
-                .setTask(fullscreenTask).build();
-
-        fullscreenTask.moveTaskToBack(task);
-
-        // Ensure full screen task has both tasks.
-        ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
-        assertEquals(task.getTopMostActivity(), secondActivity);
-        firstActivity.setState(STOPPED, "testMovingBottomMostRootTaskActivityToPinnedRootTask");
-
-
-        // Move first activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
-
-        assertTrue(firstActivity.mRequestForceTransition);
-    }
-
-    @Test
     public void testMultipleActivitiesTaskEnterPip() {
         // Enable shell transition because the order of setting windowing mode is different.
         registerTestTransitionPlayer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index dba463a..95bca2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1811,9 +1811,9 @@
         }
         addStatusBar(mActivity.mDisplayContent);
 
-        mActivity.setVisible(false);
-        mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
-        mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+        mActivity.setVisibleRequested(false);
+        requestTransition(mActivity, WindowManager.TRANSIT_OPEN);
+        mActivity.setVisibility(true);
         final float maxAspect = 1.8f;
         prepareUnresizable(mActivity, maxAspect, SCREEN_ORIENTATION_LANDSCAPE);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 7dba142..2544550 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -22,6 +22,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -69,7 +70,6 @@
 
     private MyAnimatable mAnimatable;
     private MyAnimatable mAnimatable2;
-    private DeferFinishAnimatable mDeferFinishAnimatable;
 
     @Before
     public void setUp() throws Exception {
@@ -77,14 +77,12 @@
 
         mAnimatable = new MyAnimatable(mWm, mTransaction);
         mAnimatable2 = new MyAnimatable(mWm, mTransaction);
-        mDeferFinishAnimatable = new DeferFinishAnimatable(mWm, mTransaction);
     }
 
     @After
     public void tearDown() {
         mAnimatable = null;
         mAnimatable2 = null;
-        mDeferFinishAnimatable = null;
     }
 
     @Test
@@ -202,41 +200,33 @@
     }
 
     @Test
-    public void testDeferFinish() {
-
-        // Start animation
-        final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
-
-        // Finish the animation but then make sure we are deferring.
-        onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
-        assertAnimating(mDeferFinishAnimatable);
-
-        // Now end defer finishing.
-        mDeferFinishAnimatable.mEndDeferFinishCallback.run();
-        assertNotAnimating(mAnimatable2);
-        assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled);
-        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mDeferFinishAnimatable.mFinishedAnimationType);
-        verify(mTransaction).remove(eq(mDeferFinishAnimatable.mLeash));
-    }
-
-    @Test
     public void testDeferFinishDoNotFinishNextAnimation() {
+        final DeferredFinishAdapter deferredFinishAdapter = new DeferredFinishAdapter();
+        spyOn(deferredFinishAdapter);
         // Start the first animation.
-        final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
-        onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, deferredFinishAdapter,
+                true /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+        assertAnimating(mAnimatable);
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        verify(deferredFinishAdapter).startAnimation(any(), any(),
+                eq(ANIMATION_TYPE_WINDOW_ANIMATION), callbackCaptor.capture());
+        final OnAnimationFinishedCallback onFinishedCallback = callbackCaptor.getValue();
+        onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+                deferredFinishAdapter);
         // The callback is the resetAndInvokeFinish in {@link SurfaceAnimator#getFinishedCallback}.
-        final Runnable firstDeferFinishCallback = mDeferFinishAnimatable.mEndDeferFinishCallback;
+        final Runnable firstDeferFinishCallback = deferredFinishAdapter.mEndDeferFinishCallback;
 
         // Start the second animation.
-        mDeferFinishAnimatable.mSurfaceAnimator.cancelAnimation();
-        startDeferFinishAnimatable(mSpec2);
-        mDeferFinishAnimatable.mFinishedCallbackCalled = false;
+        mAnimatable.mSurfaceAnimator.cancelAnimation();
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2,
+                true /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+        mAnimatable.mFinishedCallbackCalled = false;
 
-        // Simulate the first deferred callback is executed from
-        // {@link AnimatingActivityRegistry#endDeferringFinished}.
+        // Simulate the first deferred callback is executed.
         firstDeferFinishCallback.run();
         // The second animation should not be finished.
-        assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled);
+        assertFalse(mAnimatable.mFinishedCallbackCalled);
     }
 
     @Test
@@ -260,17 +250,6 @@
         verify(mTransaction).remove(eq(deferredFinishAdapter.mAnimationLeash));
     }
 
-    private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) {
-        mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim,
-                true /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
-                OnAnimationFinishedCallback.class);
-        assertAnimating(mDeferFinishAnimatable);
-        verify(anim).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
-                callbackCaptor.capture());
-        return callbackCaptor.getValue();
-    }
-
     private void assertAnimating(MyAnimatable animatable) {
         assertTrue(animatable.mSurfaceAnimator.isAnimating());
         assertNotNull(animatable.mSurfaceAnimator.getAnimation());
@@ -370,21 +349,6 @@
         };
     }
 
-    private static class DeferFinishAnimatable extends MyAnimatable {
-
-        Runnable mEndDeferFinishCallback;
-
-        DeferFinishAnimatable(WindowManagerService wm, Transaction transaction) {
-            super(wm, transaction);
-        }
-
-        @Override
-        public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
-            mEndDeferFinishCallback = endDeferFinishCallback;
-            return true;
-        }
-    }
-
     private static class DeferredFinishAdapter implements AnimationAdapter {
 
         private Runnable mEndDeferFinishCallback;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 0014465..edffab8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -32,8 +32,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -85,12 +83,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowInsets;
@@ -1055,25 +1049,6 @@
     }
 
     @Test
-    public void testTaskCanApplyAnimation() {
-        final Task rootTask = createTask(mDisplayContent);
-        final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
-        verifyWindowContainerApplyAnimation(task, activity1, activity2);
-    }
-
-    @Test
-    public void testRootTaskCanApplyAnimation() {
-        final Task rootTask = createTask(mDisplayContent);
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
-                createTaskInRootTask(rootTask, 0 /* userId */));
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
-                createTaskInRootTask(rootTask, 0 /* userId */));
-        verifyWindowContainerApplyAnimation(rootTask, activity1, activity2);
-    }
-
-    @Test
     public void testGetDisplayArea() {
         // WindowContainer
         final WindowContainer windowContainer = new WindowContainer(mWm);
@@ -1103,59 +1078,6 @@
         assertEquals(displayArea, displayArea.getDisplayArea());
     }
 
-    private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act,
-            ActivityRecord act2) {
-        // Initial remote animation for app transition.
-        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
-                new IRemoteAnimationRunner.Stub() {
-                    @Override
-                    public void onAnimationStart(@WindowManager.TransitionOldType int transit,
-                            RemoteAnimationTarget[] apps,
-                            RemoteAnimationTarget[] wallpapers,
-                            RemoteAnimationTarget[] nonApps,
-                            IRemoteAnimationFinishedCallback finishedCallback) {
-                        try {
-                            finishedCallback.onAnimationFinished();
-                        } catch (RemoteException e) {
-                            e.printStackTrace();
-                        }
-                    }
-
-                    @Override
-                    public void onAnimationCancelled() {
-                    }
-                }, 0, 0, false);
-        adapter.setCallingPidUid(123, 456);
-        wc.getDisplayContent().prepareAppTransition(TRANSIT_OPEN);
-        wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
-        spyOn(wc);
-        doReturn(true).when(wc).okToAnimate();
-
-        // Make sure animating state is as expected after applied animation.
-
-        // Animation target is promoted from act to wc. act2 is a descendant of wc, but not a source
-        // of the animation.
-        ArrayList<WindowContainer<WindowState>> sources = new ArrayList<>();
-        sources.add(act);
-        assertTrue(wc.applyAnimation(null, TRANSIT_OLD_TASK_OPEN, true, false, sources));
-
-        assertEquals(act, wc.getTopMostActivity());
-        assertTrue(wc.isAnimating());
-        assertTrue(wc.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
-        assertTrue(wc.getAnimationSources().contains(act));
-        assertFalse(wc.getAnimationSources().contains(act2));
-        assertTrue(act.isAnimating(PARENTS));
-        assertTrue(act.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
-        assertEquals(wc, act.getAnimatingContainer(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
-
-        // Make sure animation finish callback will be received and reset animating state after
-        // animation finish.
-        wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_OLD_TASK_OPEN, act);
-        verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any());
-        assertFalse(wc.isAnimating());
-        assertFalse(act.isAnimating(PARENTS));
-    }
-
     @Test
     public void testRegisterWindowContainerListener() {
         final WindowContainer container = new WindowContainer(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 1323d8a..71e84c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,7 +26,6 @@
 import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_OWN_FOCUS;
-import static android.view.Display.FLAG_PRESENTATION;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -55,7 +54,6 @@
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -102,7 +100,6 @@
 import android.util.ArraySet;
 import android.util.MergedConfiguration;
 import android.view.ContentRecordingSession;
-import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -1409,38 +1406,6 @@
         assertEquals(activityWindowInfo2, activityWindowInfo3);
     }
 
-    @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
-    @Test
-    public void testPresentationHidesActivitiesBehind() {
-        DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.copyFrom(mDisplayInfo);
-        displayInfo.flags = FLAG_PRESENTATION;
-        DisplayContent dc = createNewDisplay(displayInfo);
-        int displayId = dc.getDisplayId();
-        doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
-        ActivityRecord activity = createActivityRecord(createTask(dc));
-        assertTrue(activity.isVisible());
-
-        doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
-        int uid = 100000; // uid for non-system user
-        Session session = createTestSession(mAtm, 1234 /* pid */, uid);
-        int userId = UserHandle.getUserId(uid);
-        doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                LayoutParams.TYPE_PRESENTATION);
-
-        final IWindow clientWindow = new TestIWindow();
-        int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
-                userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
-                new InsetsSourceControl.Array(), new Rect(), new float[1]);
-        assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
-        assertFalse(activity.isVisible());
-
-        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
-        window.removeImmediately();
-        assertTrue(activity.isVisible());
-    }
-
     @Test
     public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
         doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1281be51..7030d986 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -26,6 +26,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -33,6 +34,8 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
@@ -1983,6 +1986,30 @@
         testSetAlwaysOnTop(displayArea);
     }
 
+    @Test
+    public void testConfigurationsAreEqualForOrganizer() {
+        Configuration config1 = new Configuration();
+        config1.smallestScreenWidthDp = 300;
+        config1.uiMode = UI_MODE_NIGHT_YES;
+
+        Configuration config2 = new Configuration(config1);
+        config2.uiMode = UI_MODE_NIGHT_NO;
+
+        Configuration config3 = new Configuration(config1);
+        config3.smallestScreenWidthDp = 500;
+
+        // Should be equal for non-controllable configuration changes.
+        assertTrue(WindowOrganizerController.configurationsAreEqualForOrganizer(config1, config2));
+
+        // Should be unequal for non-controllable configuration changes if the organizer is
+        // interested in that change.
+        assertFalse(WindowOrganizerController.configurationsAreEqualForOrganizer(
+                config1, config2, CONFIG_UI_MODE));
+
+        // Should be unequal for controllable configuration changes.
+        assertFalse(WindowOrganizerController.configurationsAreEqualForOrganizer(config1, config3));
+    }
+
     private void testSetAlwaysOnTop(WindowContainer wc) {
         final WindowContainerTransaction t = new WindowContainerTransaction();
         t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index b16f528..7f9e591 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -961,6 +961,15 @@
         return testPlayer;
     }
 
+    void requestTransition(WindowContainer<?> wc, int transit) {
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        if (controller.getTransitionPlayer() == null) {
+            registerTestTransitionPlayer();
+        }
+        controller.requestTransitionIfNeeded(transit, 0 /* flags */, null /* trigger */,
+                wc.mDisplayContent);
+    }
+
     /** Overrides the behavior of config_reverseDefaultRotation for the given display. */
     void setReverseDefaultRotation(DisplayContent dc, boolean reverse) {
         final DisplayRotation displayRotation = dc.getDisplayRotation();
@@ -1417,7 +1426,9 @@
             activity.setProcess(wpc);
 
             // Resume top activities to make sure all other signals in the system are connected.
-            mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+            if (mVisible) {
+                mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+            }
             return activity;
         }
     }
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 55a8923..86468b0 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -200,7 +200,11 @@
         mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
     }
 
-    private void updateContaminantNotification() {
+    private void updateContaminantNotificationLocked() {
+        if (mNotificationManager == null) {
+            return;
+        }
+
         PortInfo currentPortInfo = null;
         Resources r = mContext.getResources();
         int contaminantStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
@@ -1171,7 +1175,7 @@
     private void handlePortLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         sendPortChangedBroadcastLocked(portInfo);
         logToStatsd(portInfo, pw);
-        updateContaminantNotification();
+        updateContaminantNotificationLocked();
     }
 
     private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
@@ -1433,6 +1437,9 @@
                 case MSG_SYSTEM_READY: {
                     mNotificationManager = (NotificationManager)
                             mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                    synchronized (mLock) {
+                        updateContaminantNotificationLocked();
+                    }
                     break;
                 }
             }
diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml
index 37321ad8..758852b 100644
--- a/tests/AttestationVerificationTest/AndroidManifest.xml
+++ b/tests/AttestationVerificationTest/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.security.attestationverification">
 
-    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="34" />
     <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
 
     <application>
diff --git a/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json b/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json
new file mode 100644
index 0000000..2a3ba5e
--- /dev/null
+++ b/tests/AttestationVerificationTest/assets/test_revocation_list_no_test_certs.json
@@ -0,0 +1,12 @@
+{
+  "entries": {
+    "6681152659205225093" : {
+      "status": "REVOKED",
+      "reason": "KEY_COMPROMISE"
+    },
+    "8350192447815228107" : {
+      "status": "REVOKED",
+      "reason": "KEY_COMPROMISE"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json b/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json
new file mode 100644
index 0000000..e22a834
--- /dev/null
+++ b/tests/AttestationVerificationTest/assets/test_revocation_list_with_test_certs.json
@@ -0,0 +1,16 @@
+{
+  "entries": {
+    "6681152659205225093" : {
+      "status": "REVOKED",
+      "reason": "KEY_COMPROMISE"
+    },
+    "353017e73dc205a73a9c3de142230370" : {
+      "status": "REVOKED",
+      "reason": "KEY_COMPROMISE"
+    },
+    "8350192447815228107" : {
+      "status": "REVOKED",
+      "reason": "KEY_COMPROMISE"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
new file mode 100644
index 0000000..c38517a
--- /dev/null
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/CertificateRevocationStatusManagerTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2025 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.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class CertificateRevocationStatusManagerTest {
+
+    private static final String TEST_CERTIFICATE_FILE_1 = "test_attestation_with_root_certs.pem";
+    private static final String TEST_CERTIFICATE_FILE_2 = "test_attestation_wrong_root_certs.pem";
+    private static final String TEST_REVOCATION_LIST_FILE_NAME = "test_revocation_list.json";
+    private static final String REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST =
+            "test_revocation_list_no_test_certs.json";
+    private static final String REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST =
+            "test_revocation_list_with_test_certs.json";
+    private static final String TEST_REVOCATION_STATUS_FILE_NAME = "test_revocation_status.txt";
+    private static final String FILE_URL_PREFIX = "file://";
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    private CertificateFactory mFactory;
+    private List<X509Certificate> mCertificates1;
+    private List<X509Certificate> mCertificates2;
+    private File mRevocationListFile;
+    private String mRevocationListUrl;
+    private String mNonExistentRevocationListUrl;
+    private File mRevocationStatusFile;
+    private CertificateRevocationStatusManager mCertificateRevocationStatusManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mFactory = CertificateFactory.getInstance("X.509");
+        mCertificates1 = getCertificateChain(TEST_CERTIFICATE_FILE_1);
+        mCertificates2 = getCertificateChain(TEST_CERTIFICATE_FILE_2);
+        mRevocationListFile = new File(mContext.getFilesDir(), TEST_REVOCATION_LIST_FILE_NAME);
+        mRevocationListUrl = FILE_URL_PREFIX + mRevocationListFile.getAbsolutePath();
+        File noSuchFile = new File(mContext.getFilesDir(), "file_does_not_exist");
+        mNonExistentRevocationListUrl = FILE_URL_PREFIX + noSuchFile.getAbsolutePath();
+        mRevocationStatusFile = new File(mContext.getFilesDir(), TEST_REVOCATION_STATUS_FILE_NAME);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mRevocationListFile.delete();
+        mRevocationStatusFile.delete();
+    }
+
+    @Test
+    public void checkRevocationStatus_doesNotExistOnRemoteRevocationList_noException()
+            throws Exception {
+        copyFromAssetToFile(
+                REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+    }
+
+    @Test
+    public void checkRevocationStatus_existsOnRemoteRevocationList_throwsException()
+            throws Exception {
+        copyFromAssetToFile(
+                REVOCATION_LIST_WITH_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+        assertThrows(
+                CertPathValidatorException.class,
+                () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+    }
+
+    @Test
+    public void
+            checkRevocationStatus_cannotReachRemoteRevocationList_noStoredStatus_throwsException()
+                    throws Exception {
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+        assertThrows(
+                CertPathValidatorException.class,
+                () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+    }
+
+    @Test
+    public void checkRevocationStatus_savesRevocationStatus() throws Exception {
+        copyFromAssetToFile(
+                REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mRevocationListUrl, mRevocationStatusFile, false);
+
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+
+        assertThat(mRevocationStatusFile.length()).isGreaterThan(0);
+    }
+
+    @Test
+    public void checkRevocationStatus_cannotReachRemoteList_certsSaved_noException()
+            throws Exception {
+        // call checkRevocationStatus once to save the revocation status
+        copyFromAssetToFile(
+                REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mRevocationListUrl, mRevocationStatusFile, false);
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+        // call checkRevocationStatus again with mNonExistentRevocationListUrl
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+    }
+
+    @Test
+    public void checkRevocationStatus_cannotReachRemoteList_someCertsNotSaved_exception()
+            throws Exception {
+        // call checkRevocationStatus once to save the revocation status for mCertificates2
+        copyFromAssetToFile(
+                REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mRevocationListUrl, mRevocationStatusFile, false);
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates2);
+        // call checkRevocationStatus again with mNonExistentRevocationListUrl, this time for
+        // mCertificates1
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+
+        assertThrows(
+                CertPathValidatorException.class,
+                () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+    }
+
+    @Test
+    public void checkRevocationStatus_cannotReachRemoteList_someCertsStatusTooOld_exception()
+            throws Exception {
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime expiredStatusDate =
+                now.minusDays(CertificateRevocationStatusManager.MAX_DAYS_SINCE_LAST_CHECK + 1);
+        Map<String, LocalDateTime> lastRevocationCheckData = new HashMap<>();
+        lastRevocationCheckData.put(getSerialNumber(mCertificates1.get(0)), expiredStatusDate);
+        for (int i = 1; i < mCertificates1.size(); i++) {
+            lastRevocationCheckData.put(getSerialNumber(mCertificates1.get(i)), now);
+        }
+        mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastRevocationCheckData);
+
+        assertThrows(
+                CertPathValidatorException.class,
+                () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+    }
+
+    @Test
+    public void checkRevocationStatus_cannotReachRemoteList_allCertResultsFresh_noException()
+            throws Exception {
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+        LocalDateTime bearlyNotExpiredStatusDate =
+                LocalDateTime.now()
+                        .minusDays(
+                                CertificateRevocationStatusManager.MAX_DAYS_SINCE_LAST_CHECK - 1);
+        Map<String, LocalDateTime> lastRevocationCheckData = new HashMap<>();
+        for (X509Certificate certificate : mCertificates1) {
+            lastRevocationCheckData.put(getSerialNumber(certificate), bearlyNotExpiredStatusDate);
+        }
+        mCertificateRevocationStatusManager.storeLastRevocationCheckData(lastRevocationCheckData);
+
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+    }
+
+    @Test
+    public void updateLastRevocationCheckData_correctlySavesStatus() throws Exception {
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mNonExistentRevocationListUrl, mRevocationStatusFile, false);
+        Map<String, Boolean> areCertificatesRevoked = new HashMap<>();
+        for (X509Certificate certificate : mCertificates1) {
+            areCertificatesRevoked.put(getSerialNumber(certificate), false);
+        }
+
+        mCertificateRevocationStatusManager.updateLastRevocationCheckData(areCertificatesRevoked);
+
+        // no exception
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+        // revoke one certificate and try again
+        areCertificatesRevoked.put(getSerialNumber(mCertificates1.getLast()), true);
+        mCertificateRevocationStatusManager.updateLastRevocationCheckData(areCertificatesRevoked);
+        assertThrows(
+                CertPathValidatorException.class,
+                () -> mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1));
+    }
+
+    @Test
+    public void updateLastRevocationCheckDataForAllPreviouslySeenCertificates_updatesCorrectly()
+            throws Exception {
+        copyFromAssetToFile(
+                REVOCATION_LIST_WITHOUT_CERTIFICATES_USED_IN_THIS_TEST, mRevocationListFile);
+        mCertificateRevocationStatusManager =
+                new CertificateRevocationStatusManager(
+                        mContext, mRevocationListUrl, mRevocationStatusFile, false);
+        // populate the revocation status file
+        mCertificateRevocationStatusManager.checkRevocationStatus(mCertificates1);
+        // Sleep for 2 second so that the current time changes
+        SystemClock.sleep(2000);
+        LocalDateTime timestampBeforeUpdate = LocalDateTime.now();
+        JSONObject revocationList = mCertificateRevocationStatusManager.fetchRemoteRevocationList();
+        List<String> otherCertificatesToCheck = new ArrayList<>();
+        String serialNumber1 = "1234567"; // not revoked
+        String serialNumber2 = "8350192447815228107"; // revoked
+        String serialNumber3 = "987654"; // not revoked
+        otherCertificatesToCheck.add(serialNumber1);
+        otherCertificatesToCheck.add(serialNumber2);
+        otherCertificatesToCheck.add(serialNumber3);
+
+        mCertificateRevocationStatusManager
+                .updateLastRevocationCheckDataForAllPreviouslySeenCertificates(
+                        revocationList, otherCertificatesToCheck);
+
+        Map<String, LocalDateTime> lastRevocationCheckData =
+                mCertificateRevocationStatusManager.getLastRevocationCheckData();
+        assertThat(lastRevocationCheckData.get(serialNumber1)).isAtLeast(timestampBeforeUpdate);
+        assertThat(lastRevocationCheckData).doesNotContainKey(serialNumber2); // revoked
+        assertThat(lastRevocationCheckData.get(serialNumber3)).isAtLeast(timestampBeforeUpdate);
+        // validate that the existing certificates on the file got updated too
+        for (X509Certificate certificate : mCertificates1) {
+            assertThat(lastRevocationCheckData.get(getSerialNumber(certificate)))
+                    .isAtLeast(timestampBeforeUpdate);
+        }
+    }
+
+    private List<X509Certificate> getCertificateChain(String fileName) throws Exception {
+        Collection<? extends Certificate> certificates =
+                mFactory.generateCertificates(mContext.getResources().getAssets().open(fileName));
+        ArrayList<X509Certificate> x509Certs = new ArrayList<>();
+        for (Certificate cert : certificates) {
+            x509Certs.add((X509Certificate) cert);
+        }
+        return x509Certs;
+    }
+
+    private void copyFromAssetToFile(String assetFileName, File targetFile) throws Exception {
+        byte[] data;
+        try (InputStream in = mContext.getResources().getAssets().open(assetFileName)) {
+            data = in.readAllBytes();
+        }
+        try (FileOutputStream fileOutputStream = new FileOutputStream(targetFile)) {
+            fileOutputStream.write(data);
+        }
+    }
+
+    private String getSerialNumber(X509Certificate certificate) {
+        return certificate.getSerialNumber().toString(16);
+    }
+}
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 12670cd..ac704e5 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -52,10 +52,12 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
         <option name="teardown-command"
                 value="settings delete secure show_ime_with_hard_keyboard"/>
         <option name="teardown-command" value="settings delete system show_touches"/>
         <option name="teardown-command" value="settings delete system pointer_location"/>
+        <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
         <option name="teardown-command"
                 value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
     </target_preparer>
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 481a8bb..1b2007d 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -50,10 +50,12 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
         <option name="teardown-command"
                 value="settings delete secure show_ime_with_hard_keyboard"/>
         <option name="teardown-command" value="settings delete system show_touches"/>
         <option name="teardown-command" value="settings delete system pointer_location"/>
+        <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
         <option name="teardown-command"
                 value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
     </target_preparer>
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
index ae32bda..bcff2fc 100644
--- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -16,17 +16,11 @@
 
 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
-import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.hardware.input.Flags
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.junit.MockitoJUnitRunner
@@ -46,9 +40,6 @@
         const val HEIGHT = 100
     }
 
-    @get:Rule
-    val setFlagsRule = SetFlagsRule()
-
     private fun createDrawable(): Drawable? {
         val context = ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())
         val inputManager = context.getSystemService(InputManager::class.java)!!
@@ -56,16 +47,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
     fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
         val drawable = createDrawable()!!
         assertEquals(WIDTH, drawable.intrinsicWidth)
         assertEquals(HEIGHT, drawable.intrinsicHeight)
     }
-
-    @Test
-    @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
-    fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
-        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 c2f9adf..cc58bbc 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -21,9 +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
 import androidx.test.core.app.ApplicationProvider
 import com.android.server.testutils.any
@@ -50,12 +48,9 @@
  */
 @Presubmit
 @RunWith(MockitoJUnitRunner::class)
-@EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
 class StickyModifierStateListenerTest {
 
     @get:Rule
-    val rule = SetFlagsRule()
-    @get:Rule
     val inputManagerRule = MockInputManagerRule()
 
     private val testLooper = TestLooper()
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 5259455..b22e42d 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -559,9 +559,6 @@
 
     @Test
     fun handleKeyGestures_a11yBounceKeysShortcut() {
-        ExtendedMockito.doReturn(true).`when` {
-            InputSettings.isAccessibilityBounceKeysFeatureEnabled()
-        }
         val toggleBounceKeysEvent =
             KeyGestureEvent.Builder()
                 .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS)
@@ -594,9 +591,6 @@
 
     @Test
     fun handleKeyGestures_a11yStickyKeysShortcut() {
-        ExtendedMockito.doReturn(true).`when` {
-            InputSettings.isAccessibilityStickyKeysFeatureEnabled()
-        }
         val toggleStickyKeysEvent =
             KeyGestureEvent.Builder()
                 .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS)
@@ -610,9 +604,6 @@
 
     @Test
     fun handleKeyGestures_a11ySlowKeysShortcut() {
-        ExtendedMockito.doReturn(true).`when` {
-            InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
-        }
         val toggleSlowKeysEvent =
             KeyGestureEvent.Builder()
                 .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS)
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index de47f01..88e8496 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -754,9 +754,6 @@
     @EnableFlags(
         com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
         com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
         com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
@@ -773,9 +770,6 @@
     @EnableFlags(
         com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
         com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
         com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
index e855786..e1294b1 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
@@ -19,12 +19,9 @@
 import android.content.Context
 import android.hardware.input.KeyboardLayout
 import android.os.LocaleList
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.hardware.input.Flags
 import java.util.Locale
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.RuleChain
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import platform.test.screenshot.DeviceEmulationSpec
@@ -38,18 +35,14 @@
         fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
     }
 
-    val setFlagsRule = SetFlagsRule()
+    @get:Rule
     val screenshotRule = InputScreenshotTestRule(
             emulationSpec,
             "frameworks/base/tests/InputScreenshotTest/assets"
     )
 
-    @get:Rule
-    val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
-
     @Test
     fun test() {
-        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         screenshotRule.screenshotTest("layout-preview-ansi") {
             context: Context -> LayoutPreview.createLayoutPreview(
                 context,
@@ -66,5 +59,4 @@
             )
         }
     }
-
 }
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
index ab7bb4e..ddad6de 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -17,14 +17,8 @@
 package com.android.input.screenshot
 
 import android.content.Context
-import android.hardware.input.KeyboardLayout
-import android.os.LocaleList
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.hardware.input.Flags
-import java.util.Locale
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.RuleChain
 import org.junit.runner.RunWith
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -39,21 +33,16 @@
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
 
-    val setFlagsRule = SetFlagsRule()
+    @get:Rule
     val screenshotRule = InputScreenshotTestRule(
             emulationSpec,
             "frameworks/base/tests/InputScreenshotTest/assets"
     )
 
-    @get:Rule
-    val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
-
     @Test
     fun test() {
-        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         screenshotRule.screenshotTest("layout-preview") {
             context: Context -> LayoutPreview.createLayoutPreview(context, null)
         }
     }
-
 }
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
index 5231c14..8a8e4f0 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
@@ -19,12 +19,9 @@
 import android.content.Context
 import android.hardware.input.KeyboardLayout
 import android.os.LocaleList
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.hardware.input.Flags
 import java.util.Locale
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.RuleChain
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import platform.test.screenshot.DeviceEmulationSpec
@@ -38,18 +35,14 @@
         fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
     }
 
-    val setFlagsRule = SetFlagsRule()
+    @get:Rule
     val screenshotRule = InputScreenshotTestRule(
             emulationSpec,
             "frameworks/base/tests/InputScreenshotTest/assets"
     )
 
-    @get:Rule
-    val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
-
     @Test
     fun test() {
-        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         screenshotRule.screenshotTest("layout-preview-jis") {
             context: Context -> LayoutPreview.createLayoutPreview(
                 context,
@@ -66,5 +59,4 @@
             )
         }
     }
-
 }
\ No newline at end of file
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 3ee6dc4..1273826 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -16,6 +16,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -24,7 +25,7 @@
 import android.os.TestLooperManager;
 import android.util.ArrayMap;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.runners.model.FrameworkMethod;
 
@@ -33,8 +34,11 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayDeque;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Queue;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -67,16 +71,38 @@
     private Handler mHandler;
     private TestLooperManager mQueueWrapper;
 
+    /**
+     * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+     */
+    private static boolean isAtLeastBaklava() {
+        Method[] methods = TestLooperManager.class.getMethods();
+        for (Method method : methods) {
+            if (method.getName().equals("peekWhen")) {
+                return true;
+            }
+        }
+        return false;
+        // TODO(shayba): delete the above, uncomment the below.
+        // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
+        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+    }
+
     static {
-        try {
-            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
-            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
-            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
-            MESSAGE_NEXT_FIELD.setAccessible(true);
-            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
-            MESSAGE_WHEN_FIELD.setAccessible(true);
-        } catch (NoSuchFieldException e) {
-            throw new RuntimeException("Failed to initialize TestableLooper", e);
+        if (isAtLeastBaklava()) {
+            MESSAGE_QUEUE_MESSAGES_FIELD = null;
+            MESSAGE_NEXT_FIELD = null;
+            MESSAGE_WHEN_FIELD = null;
+        } else {
+            try {
+                MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+                MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+                MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+                MESSAGE_NEXT_FIELD.setAccessible(true);
+                MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+                MESSAGE_WHEN_FIELD.setAccessible(true);
+            } catch (NoSuchFieldException e) {
+                throw new RuntimeException("Failed to initialize TestableLooper", e);
+            }
         }
     }
 
@@ -222,8 +248,61 @@
     }
 
     public void moveTimeForward(long milliSeconds) {
+        if (isAtLeastBaklava()) {
+            moveTimeForwardBaklava(milliSeconds);
+        } else {
+            moveTimeForwardLegacy(milliSeconds);
+        }
+    }
+
+    private void moveTimeForwardBaklava(long milliSeconds) {
+        // Drain all Messages from the queue.
+        Queue<Message> messages = new ArrayDeque<>();
+        while (true) {
+            Message message = mQueueWrapper.poll();
+            if (message == null) {
+                break;
+            }
+
+            // Adjust the Message's delivery time.
+            long newWhen = message.when - milliSeconds;
+            if (newWhen < 0) {
+                newWhen = 0;
+            }
+            message.when = newWhen;
+            messages.add(message);
+        }
+
+        // Repost all Messages back to the queuewith a new time.
+        while (true) {
+            Message message = messages.poll();
+            if (message == null) {
+                break;
+            }
+
+            Runnable callback = message.getCallback();
+            Handler handler = message.getTarget();
+            long when = message.getWhen();
+
+            // The Message cannot be re-enqueued because it is marked in use.
+            // Make a copy of the Message and recycle the original.
+            // This resets {@link Message#isInUse()} but retains all other content.
+            {
+                Message newMessage = Message.obtain();
+                newMessage.copyFrom(message);
+                newMessage.setCallback(callback);
+                mQueueWrapper.recycle(message);
+                message = newMessage;
+            }
+
+            // Send the Message back to its Handler to be re-enqueued.
+            handler.sendMessageAtTime(message, when);
+        }
+    }
+
+    private void moveTimeForwardLegacy(long milliSeconds) {
         try {
-            Message msg = getMessageLinkedList();
+            Message msg = (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(mLooper.getQueue());
             while (msg != null) {
                 long updatedWhen = msg.getWhen() - milliSeconds;
                 if (updatedWhen < 0) {
@@ -237,17 +316,6 @@
         }
     }
 
-    private Message getMessageLinkedList() {
-        try {
-            MessageQueue queue = mLooper.getQueue();
-            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(
-                    "Access failed in TestableLooper: get - MessageQueue.mMessages",
-                    e);
-        }
-    }
-
     private int processQueuedMessages() {
         int count = 0;
         Runnable barrierRunnable = () -> { };